package com.yanqu.xiuxian.crosssystem.manger.elementalbonds;

import com.google.common.base.Stopwatch;
import com.google.protobuf.AbstractMessage;
import com.yanqu.xiuxian.common.ServerIdMgr;
import com.yanqu.xiuxian.config.backend.BusinessServerParamConfigParam;
import com.yanqu.xiuxian.config.backend.GroupResult;
import com.yanqu.xiuxian.config.backend.RoundMatchInfo;
import com.yanqu.xiuxian.config.backend.RoundServerList;
import com.yanqu.xiuxian.config.elementalbonds.ElementalBondsSeasonConfig;
import com.yanqu.xiuxian.config.elementalbonds.*;
import com.yanqu.xiuxian.config.elementalbonds.enums.*;
import com.yanqu.xiuxian.config.utils.excel.StringUtils;
import com.yanqu.xiuxian.crosssystem.logic.CrossSystemGroupRule;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.data.*;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.entity.*;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.enums.ElementalBondsSceneType;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.enums.ElementalBondsSkillType;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.logic.ElementalBondsAiLogic;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.logic.ElementalBondsGameLogic;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.logic.ElementalBondsGuideLogic;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.logic.ElementalBondsSceneLogic;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.msg.ElementalBondsNoticeHelper;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.param.ElementalBondsCardParam;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.param.ElementalBondsMoveParam;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.param.ElementalBondsSkillParam;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.pb.CrossElementalBondsPb;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.result.ElementalBondsOperateResult;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.result.ElementalBondsOperateRoundResult;
import com.yanqu.xiuxian.crosssystem.manger.elementalbonds.result.ElementalBondsOperateStageResult;
import com.yanqu.xiuxian.db.MongoRepository;
import com.yanqu.xiuxian.entity.enums.SystemId;
import com.yanqu.xiuxian.entity.enums.chat.eMessageType;
import com.yanqu.xiuxian.entity.log.elementalbonds.ElementalBondsOperateLog;
import com.yanqu.xiuxian.entity.redis.player.PlayerShowData;
import com.yanqu.xiuxian.entity.server.ServerAndAddressInfo;
import com.yanqu.xiuxian.entity.server.ServerAndAddressInfoList;
import com.yanqu.xiuxian.framework.common.log.LogMgr;
import com.yanqu.xiuxian.framework.config.CrossServerConfigMgr;
import com.yanqu.xiuxian.framework.connect.CrossAgentLinkedControl;
import com.yanqu.xiuxian.framework.mgr.AbsSystemMgr;
import com.yanqu.xiuxian.framework.timer.LogicTimerMgr;
import com.yanqu.xiuxian.message.YanQuMessage;
import com.yanqu.xiuxian.pb.PbDefine;
import com.yanqu.xiuxian.pb.activity.ActivityBase;
import com.yanqu.xiuxian.pb.common.PlayerInfoPb;
import com.yanqu.xiuxian.pb.cross.system.CrossElementalBonds;
import com.yanqu.xiuxian.pb.cross.system.CrossElementalBonds.ChannelElementalBondsMatchReqMsg;
import com.yanqu.xiuxian.pb.cross.system.CrossElementalBonds.ChannelElementalBondsStatusRespMsg;
import com.yanqu.xiuxian.pb.cross.system.CrossElementalBonds.ChannelElementalBondsSyncDaySceneRespMsg;
import com.yanqu.xiuxian.pb.cross.system.CrossElementalBonds.ChannelElementalBondsUserKeyMsg;
import com.yanqu.xiuxian.pb.elementalbonds.ElementalBonds;
import com.yanqu.xiuxian.pb.elementalbonds.ElementalBonds.*;
import com.yanqu.xiuxian.pb.player.Rank;
import com.yanqu.xiuxian.pb.player.WorldMessage.WorldMessageListMsg;
import com.yanqu.xiuxian.pb.player.WorldMessage.WorldMessageTempMsg;
import com.yanqu.xiuxian.redis.PlayerRedisMgr;
import com.yanqu.xiuxian.server.protocol.GameErrorCode;
import com.yanqu.xiuxian.utils.*;
import com.yanqu.xiuxian.utils.date.DateHelper;
import io.netty.util.internal.ConcurrentSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class CrossElementalBondsMgr extends AbsSystemMgr {

    private static final Logger logger = LoggerFactory.getLogger(CrossElementalBondsMgr.class);

    private static CrossElementalBondsMgr instance = new CrossElementalBondsMgr(SystemId.ElementalBonds);

    public CrossElementalBondsMgr(SystemId systemId) {
        super(systemId);
    }

    public static CrossElementalBondsMgr getInstance() {
        return instance;
    }

    private ElementBondsSystemInfo elementBondsSystemInfo = new ElementBondsSystemInfo();

    public static final String DECORATION_SEPARATOR = "@#$";
    public static final String DECORATION_SPLIT_SEPARATOR = "@#\\$";

    private final int SHOW_OPPONENT_NUM = 10;                                                           // 匹配展示玩家数
    private final long SHOW_MAX_MATCH_TIME = 30 * DateHelper.SECOND_MILLIONS;                            // 匹配最大预计时间
    private int MATCH_TIME_PARAM = 50;                                                            // 匹配预计时间取最近几次的平均值


    private final Object lock = new Object();
    private final AtomicLong roomMaxId = new AtomicLong(100);
    private final Random needLockRandom = new Random(); // 锁自己

    private final Map<String, ElementalBondsMatchData> matchingUserMap = new ConcurrentHashMap<>(); // 锁lock
    private final Map<String, ElementalBondsMatchData> roomUserMap = new ConcurrentHashMap<>(); // 锁lock
    private final Map<Long, ElementalBondsRoomData> roomMap = new ConcurrentHashMap<>(); // 锁自己
    private final LinkedList<Long> matchTimeList = new LinkedList<>();  // 锁自己
    //k:分组id,v:分组信息
    private final ConcurrentHashMap<Long, CrossElementalBondsGroupMgr> groupMgrMap = new ConcurrentHashMap<>(); //
    //区服id,分组id
    private final ConcurrentHashMap<Long, Long> serverGroupMap = new ConcurrentHashMap<>(); // 分组信息
    //赛季配置 k:赛季id v:赛季时间配置
    public ConcurrentHashMap<Integer, ElementalBondsSeasonConfig> seasonConfigMap = new ConcurrentHashMap<>();
    //总排行榜
    private List<ElementalBondsRankData> rankList = new ArrayList<>();

    //k:分组id,v:聊天信息
    private Map<Long, List<WorldMessageTempMsg.Builder>> chatMessageMap = new ConcurrentHashMap<>();

    private ConcurrentSet<Long> syncedServerIdSet = new ConcurrentSet<>();

    @Override
    public boolean reloadData() {
        // 先加载出今天的数据
        ElementBondsSystemInfo systemInfo = MongoRepository.selectOne(new Query(), ElementBondsSystemInfo.class);
        if (systemInfo == null) {
            systemInfo = new ElementBondsSystemInfo();
        }
        elementBondsSystemInfo = systemInfo;
        reloadSeasonConfig();

        List<ElementalBondsGroupData> groupDataList = MongoRepository.selectList(new Query(Criteria.where("createTime").gte(systemInfo.getSeasonStartTime())), ElementalBondsGroupData.class);
        for (ElementalBondsGroupData groupData : groupDataList) {
            CrossElementalBondsGroupMgr groupMgr = new CrossElementalBondsGroupMgr(groupData);
            groupMgr.reloadData();
            for (Long serverId : groupData.getServerIdList()) {
                serverGroupMap.put(serverId,groupData.getGroupId());
            }

            groupMgrMap.put(groupData.getGroupId(), groupMgr);
        }
        buildFinalRank(true);
        return true;
    }

    @Override
    public boolean saveData(boolean force) {
        MongoRepository.save(elementBondsSystemInfo);
        for (CrossElementalBondsGroupMgr groupMgr : groupMgrMap.values()) {
            groupMgr.saveData();
        }
        return true;
    }

    @Override
    public boolean registerTimerTask() {
        LogicTimerMgr.getInstance().addTimerTask(this::group, 1, 10, TimeUnit.SECONDS);
        LogicTimerMgr.getInstance().addTimerTask(this::clearExpireData, 1, 10, TimeUnit.MINUTES);
        LogicTimerMgr.getInstance().addTimerTask(this::matchByTimer, 1, 3, TimeUnit.SECONDS);
        LogicTimerMgr.getInstance().addTimerTask(this::checkExpire, 1, 1, TimeUnit.SECONDS);
        LogicTimerMgr.getInstance().addTimerTask(this::checkRefreshSceneTimer, 1, 1, TimeUnit.SECONDS);
        LogicTimerMgr.getInstance().addTimerTask(this::seasonEnd, 1, 5, TimeUnit.SECONDS);
        LogicTimerMgr.getInstance().addTimerTask(this::syncServerRound, 1, 10, TimeUnit.SECONDS);
        return true;
    }

    public void group() {
        try {
            //判断赛季时间
            if (!isOpen()) {
                return;
            }
            int todayWeekId = TimeUtils.getTodayWeek();
            //if (todayWeekId != 4) return; TODO:分组期 临时屏蔽
            synchronized (lock) {
                long now = TimeUtils.currentTimeMillis();
                if (TimeUtils.isSameWeek(elementBondsSystemInfo.getGroupTime())) return;
                //判断是否跨赛季了检查赛季切换
                long zeroSubTime = now - DateHelper.getZeroTimeStamp(now);
                //TODO:分组期
                boolean isNewSeason = false;
                ElementalBondsSeasonConfig bondsSeasonConfig = getElementalBondsSeasonConfig(now);
                if (bondsSeasonConfig == null) {
                    return;
                }
                if (bondsSeasonConfig.getSeasonId() != elementBondsSystemInfo.getSeasonId()) {
                    LogUtils.COMMON.info("handle elementalbonds season change...");
                    reloadSeasonConfig();
                    isNewSeason = true;
                }
                // 2.算出所有区服的轮次
                ServerAndAddressInfoList serverAndAddressInfoList = CrossSystemGroupRule.getServerAndAddressInfoList("CrossElementBonds");
                if (serverAndAddressInfoList == null || serverAndAddressInfoList.getServerAndAddressInfoMap().isEmpty()) {
                    return;
                }

                Map<Integer, RoundServerList> newRoundMap = new HashMap<>();
                if (isNewSeason) {
                    //清数据
                    serverGroupMap.clear();
                    groupMgrMap.clear();
                    rankList.clear();
                    MongoRepository.deleteList(new Query(), ElementalBondsGroupData.class);
                    MongoRepository.deleteList(new Query(), ElementalBondsRankData.class);
                    elementBondsSystemInfo.setSeasonStartTime(bondsSeasonConfig.getBeginShowTime());
                    elementBondsSystemInfo.setSeasonEndTime(bondsSeasonConfig.getEndShowTime());
                    elementBondsSystemInfo.setServerWaitMap(new ConcurrentHashMap<>());
                    elementBondsSystemInfo.setSceneList(new ArrayList<>());
                    elementBondsSystemInfo.setSceneWeightMap(new ConcurrentHashMap<>());

                    elementBondsSystemInfo.setSeasonStartTime(bondsSeasonConfig.getBeginShowTime());
                    elementBondsSystemInfo.setSeasonEndTime(bondsSeasonConfig.getEndShowTime());
                }
                Set<Long> hasGroupServerIdList = new HashSet<>();
                for (CrossElementalBondsGroupMgr groupInfo : groupMgrMap.values()) {
                    hasGroupServerIdList.addAll(groupInfo.getServerIdList());
                }
                long time = TimeUtils.getWeekBeginTimestamp() + 4 * TimeUtils.DAY_MILLIS;
                for (ServerAndAddressInfo serverAndAddressInfo : serverAndAddressInfoList.getServerAndAddressInfoMap().values()) {
                    if (now < serverAndAddressInfo.getServerInfo().getOpenTime()) {
                        continue;
                    }
                    // 补充规则：开放时间跟分组时间是同一天就不分组
                    if (TimeUtils.isSameDay(now, serverAndAddressInfo.getServerInfo().getOpenTime())) {
                        continue;
                    }
                    if (serverAndAddressInfo.getServerAddress() == null || serverAndAddressInfo.getServerAddress().getCrossServerIds() == null) {
                        continue;
                    }
                    if (!serverAndAddressInfo.getServerAddress().getCrossServerIds().contains(CrossServerConfigMgr.getInstance().getConfig().getServerAddressRsp().getCrossServerId())) {
                        continue;
                    }
                    long serverId = serverAndAddressInfo.getServerInfo().getServerId();
                    if (hasGroupServerIdList.contains(serverId)) {
                        // 已有分组
                        continue;
                    }

                    if (elementBondsSystemInfo.getServerWaitMap().containsKey(serverId)) {
                        //  需要特殊处理
                        continue;
                    }

                    int round = ServerIdMgr.calRoundNum(serverAndAddressInfo.getServerInfo().getOpenTime(), time, serverAndAddressInfo.getServerInfo().getFirstRound(), BusinessServerParamConfigParam.CHANNEL_SECOND_ROUND_CONFIG);
                    RoundServerList roundServerList = null;
                    roundServerList = newRoundMap.computeIfAbsent(round, k -> new RoundServerList());
                    roundServerList.setRound(round);
                    roundServerList.getServerList().add(serverId);
                }
                if (isNewSeason) {
                    Map<Long, ElementalBondsGroupData> groupDataMap = roundGroup(newRoundMap, now, 0);
                    for (ElementalBondsGroupData elementalBondsGroupData : groupDataMap.values()) {
                        CrossElementalBondsGroupMgr groupMgr = new CrossElementalBondsGroupMgr(elementalBondsGroupData);
                        groupMgrMap.put(elementalBondsGroupData.getGroupId(), groupMgr);
                        for (Long serverId : elementalBondsGroupData.getServerIdList()) {
                            serverGroupMap.put(serverId,elementalBondsGroupData.getGroupId());
                        }

                    }
                }else {
                    //判断分组是否截至
                    long elementalBondsGroupEndday = Long.parseLong(BusinessServerParamConfigParam.ELEMENTAL_BONDS_GROUP_ENDDAY) * TimeUtils.MINUTE_MILLIS;
                    if (now + elementalBondsGroupEndday - elementBondsSystemInfo.getSeasonEndTime() < 0) {
                        doGroupAndMerge(newRoundMap, now);
                    }

                }
                //强制刷新
                checkRefreshScene(true);
                //同步到区服
                syncGroupToServerOnce();
                elementBondsSystemInfo.setGroupTime(now);
                elementBondsSystemInfo.setSeasonId(bondsSeasonConfig.getSeasonId());
            }


        } catch (Throwable e) {
            LogUtils.ERROR.error("夺魁 分组失败", e);
        }

    }

    private void syncGroupToServerOnce() {
        // 同步到活动内渠道
        for (CrossElementalBondsGroupMgr groupMgr : groupMgrMap.values()) {
            ElementalBondsGroupData elementalBondsGroupData = groupMgr.getElementalBondsGroupData();
            for (Long serverId : elementalBondsGroupData.getServerIdList()) {
                CrossElementalBonds.CrossElementalBondsSyncServerRoundMsg.Builder builder = CrossElementalBonds.CrossElementalBondsSyncServerRoundMsg.newBuilder();
                builder.setRound(groupMgr.getElementalBondsGroupData().getRound());

                YanQuMessage message = YanQuMessageUtils.buildMessage(ElementalBondsPb.ELEMENTAL_BONDS_SYNC_SERVER_ROUND_FROM_CROSS_VALUE, builder);
                CrossAgentLinkedControl.send(serverId, message);
            }

        }
    }

    /**
     * 存在两个情况 1.一次分组后 存在 不成组的区服 2.新开区服和上周剩下的区服 可能还是不成组
     *
     * @param newRoundMap
     * @param now
     */
    private void doGroupAndMerge(Map<Integer, RoundServerList> newRoundMap, long now) {
        String groupStr = BusinessServerParamConfigParam.ELEMENTAL_BONDS_GROUP_CONFIG;
        Map<Integer, RoundMatchInfo> roundMatchInfoMap = BusinessServerParamConfigParam.parseSeasonRoundMatchInfo(groupStr);
        long duration = Long.parseLong(BusinessServerParamConfigParam.ELEMENTAL_BONDS_GROUP_MINDAY) * TimeUtils.MINUTE_MILLIS;
        // 取出不满的分组
        TreeMap<Long, ElementalBondsGroupData> notFullGroupInfoMap = new TreeMap<>();
        Map<Long, ElementalBondsGroupData> oldGroupInfoMap = new HashMap<>();
        for (CrossElementalBondsGroupMgr groupMgr : groupMgrMap.values()) {
            oldGroupInfoMap.put(groupMgr.getGroupId(), groupMgr.getElementalBondsGroupData());
            if (groupMgr.getElementalBondsGroupData().isFull()) {
                continue;
            }
            notFullGroupInfoMap.put(groupMgr.getElementalBondsGroupData().getGroupId(), groupMgr.getElementalBondsGroupData());
        }
        long oldMaxGroupId = getUsableGroupId(oldGroupInfoMap);
        //没有不满的组 剩下的按照初始轮次直接丢进去分
        Map<Long, ElementalBondsGroupData> groupDataMap = new HashMap<>();
        if (notFullGroupInfoMap.isEmpty()) {
            //有等待的区服
            for (ElementalBondsServerWaitData value : elementBondsSystemInfo.getServerWaitMap().values()) {
                long serverId = value.getServerId();
                int round = value.getRound();
                RoundServerList roundServerList = null;
                roundServerList = newRoundMap.computeIfAbsent(round, k -> new RoundServerList());
                roundServerList.setRound(round);
                roundServerList.getServerList().add(serverId);
            }
            //和新开的放到一起试着一起分组
            groupDataMap.putAll(roundGroup(newRoundMap, now, oldMaxGroupId));
        } else {
            List<Long> serverList = new ArrayList<>(elementBondsSystemInfo.getServerWaitMap().keySet());
            for (RoundServerList roundServer : newRoundMap.values()) { //只会剩下一个轮次就是新开的
                serverList.addAll(roundServer.getServerList());
            }
            serverList.sort(Long::compare);
            Set<Long> hasGroupServerIdSet = new HashSet<>();
            //先塞不满的组
            for (ElementalBondsGroupData elementalBondsGroupData : notFullGroupInfoMap.values()) {
                RoundMatchInfo roundMatchInfo = roundMatchInfoMap.get(elementalBondsGroupData.getRound());
                int matchNum = roundMatchInfo.getMatchNum();
                //等待太久了
                if (now - elementalBondsGroupData.getCreateTime() >= duration) {
                    elementalBondsGroupData.setFull(true);
                }
                int size = elementalBondsGroupData.getServerIdList().size();
                for (int i = 0; i < matchNum - size; i++) {
                    if (serverList.isEmpty()) {
                        break;
                    }
                    Long serverId = serverList.remove(0);
                    elementBondsSystemInfo.getServerWaitMap().remove(serverId);
                    hasGroupServerIdSet.add(serverId);
                    elementalBondsGroupData.getServerIdList().add(serverId);
                    if (elementalBondsGroupData.getServerIdList().size() == matchNum) {
                        elementalBondsGroupData.setFull(true);
                        break;
                    }
                    elementalBondsGroupData.setChangeState();
                }
                if (serverList.isEmpty()) break;
            }
            //剩下的新开的区服 一起分组
            if (!serverList.isEmpty()) {
                for (RoundServerList roundServer : newRoundMap.values()) {
                    roundServer.getServerList().removeIf(hasGroupServerIdSet::contains);//已经分组的移除
                }
                if (newRoundMap.isEmpty()) {
                    for (ElementalBondsServerWaitData value : elementBondsSystemInfo.getServerWaitMap().values()) {
                        long serverId = value.getServerId();
                        int round = value.getRound();
                        RoundServerList roundServerList = null;
                        roundServerList = newRoundMap.computeIfAbsent(round, k -> new RoundServerList());
                        roundServerList.setRound(round);
                        roundServerList.getServerList().add(serverId);
                    }
                }
                groupDataMap.putAll(roundGroup(newRoundMap, now, oldMaxGroupId));
            }

        }

        if (!elementBondsSystemInfo.getServerWaitMap().isEmpty()) { //还有没分组的
            Map<Integer, RoundServerList> roundMap = new HashMap<>();
            ConcurrentHashMap<Long, ElementalBondsServerWaitData> serverWaitMap = elementBondsSystemInfo.getServerWaitMap();
            for (ElementalBondsServerWaitData value : serverWaitMap.values()) {
                if (now - value.getStartWaitTime() >= duration) {
                    long serverId = value.getServerId();
                    int round = value.getRound();
                    RoundServerList roundServerList = null;
                    roundServerList = roundMap.computeIfAbsent(round, k -> new RoundServerList());
                    roundServerList.setRound(round);
                    roundServerList.getServerList().add(serverId);
                    serverWaitMap.remove(serverId);
                }
            }
            long unusedGroupId = Math.max(getUsableGroupId(groupDataMap), oldMaxGroupId);
            for (RoundServerList roundServerList : roundMap.values()) {
                long groupId = unusedGroupId;
                ElementalBondsGroupData groupData = new ElementalBondsGroupData(roundServerList.getRound(), groupId, roundServerList.getServerList(), true, now);
                groupDataMap.put(groupId, groupData);
                unusedGroupId++;
            }
        }

        if (!groupDataMap.isEmpty()) { //分好组生成mgr
            for (ElementalBondsGroupData elementalBondsGroupData : groupDataMap.values()) {
                CrossElementalBondsGroupMgr groupMgr = new CrossElementalBondsGroupMgr(elementalBondsGroupData);
                groupMgrMap.put(elementalBondsGroupData.getGroupId(), groupMgr);
                for (Long serverId : elementalBondsGroupData.getServerIdList()) {
                    elementBondsSystemInfo.getServerWaitMap().keySet().remove(serverId);
                    serverGroupMap.put(serverId,elementalBondsGroupData.getGroupId());
                }

            }
        }
        elementBondsSystemInfo.setChangeState();
    }

    private Map<Long, ElementalBondsGroupData> roundGroup(Map<Integer, RoundServerList> roundMap, long now, long oldMaxGroupId) {
        Map<Long, ElementalBondsGroupData> groupInfoMap = new HashMap<>();
        if (roundMap.isEmpty()) {
            return groupInfoMap;
        }

        List<RoundServerList> roundServersList = new ArrayList<>(roundMap.values());
        Map<Long, Integer> needGroupServerIdMap = new HashMap<>();
        for (RoundServerList data : roundServersList) {
            for (Long serverId : data.getServerList()) {
                needGroupServerIdMap.put(serverId, data.getRound());
            }
        }
        // 3. 生成轮次对应的组
        String groupStr = BusinessServerParamConfigParam.ELEMENTAL_BONDS_GROUP_CONFIG;
        Map<Integer, RoundMatchInfo> roundMatchInfoMap = BusinessServerParamConfigParam.parseSeasonRoundMatchInfo(groupStr);

        List<GroupResult> groupResults = CrossSystemGroupRule.elementalBondsRoundServerGroup(roundServersList, roundMatchInfoMap, BusinessServerParamConfigParam.ELEMENTAL_BONDS_GROUP_MINPER);
        for (GroupResult result : groupResults) {
            int round = result.getRound();
            // 取最大id
            oldMaxGroupId++;
            long usableGroupId = oldMaxGroupId;
            // 最小区服的开服时间
            int size = result.getServerList().size();
            boolean isFull = size >= roundMatchInfoMap.get(round).getMatchNum();
            ElementalBondsGroupData groupInfo = new ElementalBondsGroupData(round, usableGroupId, result.getServerList(), isFull, now);
            result.getServerList().forEach(needGroupServerIdMap::remove);
            elementBondsSystemInfo.getServerWaitMap().keySet().removeAll(result.getServerList());
            groupInfoMap.put(groupInfo.getGroupId(), groupInfo);
        }
        //没分组的添加等待时间
        LogUtils.COMMON.info("夺魁分组 needGroupServerIdMap :{}", JsonUtils.toJSONString(needGroupServerIdMap));
        needGroupServerIdMap.forEach((serverId, roundId) -> { //丢到最低轮次1
            if (!elementBondsSystemInfo.getServerWaitMap().containsKey(serverId)) {
                elementBondsSystemInfo.getServerWaitMap().put(serverId, new ElementalBondsServerWaitData(serverId, TimeUtils.getTodayZeroTimeStamp(), 1));
            }
        });

        LogUtils.COMMON.info("夺魁分组 roundMap :{}, groupInfoMap:{},now:{},groupResults:{}", JsonUtils.toJSONString(roundMap), JsonUtils.toJSONString(groupInfoMap), now, groupResults);

        return groupInfoMap;
    }

    private long getUsableGroupId(Map<Long, ElementalBondsGroupData> groupInfoMap) {
        long maxGroupId = 0;
        for (ElementalBondsGroupData groupInfo : groupInfoMap.values()) {
            if (groupInfo.getGroupId() > maxGroupId) {
                maxGroupId = groupInfo.getGroupId();
            }
        }
        for (long i = 1; i <= maxGroupId; i++) {
            ElementalBondsGroupData groupInfo = groupInfoMap.get(i);
            if (groupInfo == null) {
                return i;
            }
        }
        return maxGroupId + 1;
    }

    private void syncServerRound() {
        if (TimeUtils.isSameWeek(elementBondsSystemInfo.getGroupTime(), TimeUtils.currentTimeMillis()) &&
                syncedServerIdSet.size() != serverGroupMap.size()) {
            // 同步到活动内渠道
            for (CrossElementalBondsGroupMgr groupMgr : groupMgrMap.values()) {
                ElementalBondsGroupData elementalBondsGroupData = groupMgr.getElementalBondsGroupData();
                for (Long serverId : elementalBondsGroupData.getServerIdList()) {
                    CrossElementalBonds.CrossElementalBondsSyncServerRoundMsg.Builder builder = CrossElementalBonds.CrossElementalBondsSyncServerRoundMsg.newBuilder();
                    builder.setRound(groupMgr.getElementalBondsGroupData().getRound());

                    YanQuMessage message = YanQuMessageUtils.buildMessage(ElementalBondsPb.ELEMENTAL_BONDS_SYNC_SERVER_ROUND_FROM_CROSS_VALUE, builder);
                    CrossAgentLinkedControl.send(serverId, message);
                }

            }
        }
    }

    public void seasonEnd() {
        //收集总榜
        buildFinalRank(false);
        seasonEndReward();
    }

    private void buildFinalRank(boolean force) {
        if (elementBondsSystemInfo.getCreateFinalRankTime() >= elementBondsSystemInfo.getSeasonEndTime() && !force)
            return;
        long nowTime = TimeUtils.currentTimeMillis();
        if (nowTime < elementBondsSystemInfo.getSeasonEndTime()) {
            return;
        }

        String rankParamStr = BusinessServerParamConfigParam.ELEMENTAL_BONDS_FINAL_RANK_PARAM;
        if (rankParamStr == null || rankParamStr.isEmpty()) {
            elementBondsSystemInfo.setCreateFinalRankTime(nowTime);
            return;
        }
        List<Integer> paramList = StringUtils.stringToIntegerList(rankParamStr, "\\|");
        int maxPerGroup = paramList.get(0);
        if (maxPerGroup == 0) {
            elementBondsSystemInfo.setCreateFinalRankTime(nowTime);
            return;
        }
        int minScore = paramList.get(1);
        //小顶堆
        PriorityQueue<ElementalBondsRankData> maxScoreHeap = new PriorityQueue<>(maxPerGroup, (a, b) -> Long.compare(b.getScore(), a.getScore()));
        for (CrossElementalBondsGroupMgr groupMgr : groupMgrMap.values()) {
            CopyOnWriteArrayList<ElementalBondsRankData> playerRankList = groupMgr.getPlayerRankList();
            if (playerRankList.isEmpty()) {
                continue;
            }
            int count = 0;
            for (ElementalBondsRankData elementalBondsRankData : playerRankList) {
                if (count >= maxPerGroup) {
                    break;
                }

                if (maxScoreHeap.size() < maxPerGroup) {
                    maxScoreHeap.offer(elementalBondsRankData);
                } else {
                    ElementalBondsRankData top = maxScoreHeap.peek();
                    if (top != null && elementalBondsRankData.getScore() > elementalBondsRankData.getScore()) {
                        maxScoreHeap.poll();
                        maxScoreHeap.offer(elementalBondsRankData);
                    }
                }
                count++;
            }
        }

        // 将优先队列转换为列表并按分数降序排列
        List<ElementalBondsRankData> topKList = new ArrayList<>(maxScoreHeap);
        topKList.sort((a, b) -> Long.compare(b.getScore(), a.getScore()));

        this.rankList = topKList;
    }

    //赛季结束 排行榜补发
    public void seasonEndReward() {
        long nowTime = TimeUtils.currentTimeMillis();
        if (nowTime > elementBondsSystemInfo.getSeasonEndTime()) { //开始发奖
            for (CrossElementalBondsGroupMgr groupMgr : groupMgrMap.values()) {
                groupMgr.sendRankReward();
            }
        }
    }

    public void reloadSeasonConfig() {
        //加载赛季配置
        Map<Integer, List<Long>> elementalBondsTime = BusinessServerParamConfigParam.ELEMENTAL_BONDS_TIME;
        if (elementalBondsTime == null || elementalBondsTime.isEmpty()) {
            throw new RuntimeException("elementalBondsTime is null");
        }
        Map<Integer, Integer> elementalBondsPeriodTime = ElementalBondsParam.getInstance().getPeriodTimeMap();
        if (elementalBondsPeriodTime == null || elementalBondsPeriodTime.isEmpty()) {
            throw new RuntimeException("elementalBondsPeriodTime is null");
        }
        long showDuration = elementalBondsPeriodTime.get(1) * TimeUtils.MINUTE_MILLIS;
        long rewardDuration = elementalBondsPeriodTime.get(2) * TimeUtils.MINUTE_MILLIS;
        for (Map.Entry<Integer, List<Long>> entry : elementalBondsTime.entrySet()) {
            int tmpSeasonId = entry.getKey();
            List<Long> timeList = entry.getValue();
            long beginShowTime = timeList.get(0);
            long endShowTime = timeList.get(1);
            ElementalBondsSeasonConfig seasonConfig = new ElementalBondsSeasonConfig();
            seasonConfig.setSeasonId(tmpSeasonId);
            seasonConfig.setBeginShowTime(beginShowTime);
            seasonConfig.setEndShowTime(endShowTime);
            seasonConfig.setPublicityEndTime(beginShowTime + showDuration);
            seasonConfig.setRewardBeginTime(endShowTime - rewardDuration);
            seasonConfigMap.put(tmpSeasonId, seasonConfig);
        }
    }

    public ElementalBondsSeasonConfig getElementalBondsSeasonConfig(long now) {
        for (ElementalBondsSeasonConfig seasonConfig : seasonConfigMap.values()) {
            if (now >= seasonConfig.getBeginShowTime() && now < seasonConfig.getEndShowTime()) {
                return seasonConfig;
            }
        }
        return null;
    }

    public void clearExpireData() {
        removeExpireErrorRoom();
    }

    public boolean isOpen() {
        int seasonId = elementBondsSystemInfo.getSeasonId();
        if (seasonId == 0) {
            seasonId = 1;
        }
        List<Long> timeList = BusinessServerParamConfigParam.ELEMENTAL_BONDS_TIME.get(seasonId);
        if (timeList == null) {
            return false;
        }
        long now = TimeUtils.currentTimeMillis();
        return now >= timeList.get(0) && now <= timeList.get(1);
    }

    // --------------------------- 游戏逻辑 --------------------------------
    private int startGame(ElementalBondsRoomData elementalBondsRoomData) {
        if (elementalBondsRoomData.isBegin()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_GAME_START;
        }
        if (elementalBondsRoomData.getPlayer1() == null || elementalBondsRoomData.getPlayer2() == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_FULL;
        }

        ElementalBondsPlayerData player1 = elementalBondsRoomData.getPlayer1();
        ElementalBondsPlayerData player2 = elementalBondsRoomData.getPlayer2();

        boolean isFriendGuide = false;
        // 随机先手玩家
        int turnPlayer;
        if (player1.getElementalBondsMatchData().isFriendGuide()) {
            turnPlayer = 1;
            isFriendGuide = true;
        } else if (player2.getElementalBondsMatchData().isFriendGuide()) {
            turnPlayer = 2;
            isFriendGuide = true;
        } else {
            synchronized (needLockRandom) {
                turnPlayer = needLockRandom.nextInt(1000) < 500 ? 1 : 2;
            }
        }
        (turnPlayer == 1 ? player1 : player2).setFirstOperate(true);

        // 拿回合时间
        ElementalBondsModeConfig elementalBondsModeConfig = ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(elementalBondsRoomData.getModeId());
        long clearTime = elementalBondsModeConfig.getClearTime();
        long expireTime = TimeUtils.currentTimeMillis() + clearTime;
        // 新手第一把多给点时间
        if (isFriendGuide) {
            expireTime = TimeUtils.currentTimeMillis() + clearTime + DateHelper.MINUTE_MILLIONS * 30;
        }

        // 拿初始盘面
        List<List<ElementalBondsBlock>> initBlockList = isFriendGuide ?
                ElementalBondsGuideLogic.getBlockList(true, turnPlayer == 1) : ElementalBondsGameLogic.getInitBlockList(new Random());
        ElementalBondsGameData elementalBondsGameData = ElementalBondsGameData.createElementalBondsGameData(elementalBondsRoomData.getAppointSceneId(), initBlockList);
        // 盘面机制生效
        ElementalBondsOperateResult elementalBondsOperateResult = ElementalBondsOperateResult.newSceneRefreshResult(turnPlayer);
        ElementalBondsSceneLogic.beginGameInit(new Random(), elementalBondsGameData, elementalBondsOperateResult);

        elementalBondsRoomData.setRound(1);
        elementalBondsRoomData.setExpireTime(expireTime);
        elementalBondsRoomData.setTurnPlayer(turnPlayer);
        elementalBondsRoomData.getPlayer1().setStep(elementalBondsModeConfig.getMaxStep());
        elementalBondsRoomData.getPlayer2().setStep(elementalBondsModeConfig.getMaxStep());
        elementalBondsRoomData.setGameData(elementalBondsGameData);
        elementalBondsRoomData.setBeginTime(TimeUtils.currentTimeMillis());
        elementalBondsRoomData.setBegin(true);

        if (isFriendGuide) {
            elementalBondsRoomData.setGuide(true);
            if (turnPlayer == 1) {
                player1.setStep(elementalBondsModeConfig.getMaxStep() + 1);
                player1.getElementalBondsCardData().setCardMp(elementalBondsRoomData.getPlayer1().getElementalBondsCardData().getCardMaxMp() - 3);
            } else {
                player2.setStep(elementalBondsModeConfig.getMaxStep() + 1);
                player2.getElementalBondsCardData().setCardMp(elementalBondsRoomData.getPlayer2().getElementalBondsCardData().getCardMaxMp() - 3);
            }
        }

        // 同步游戏开始
        elementalBondsRoomData.addShowTime(10000);
        ElementalBondsMatchData elementalBondsMatchData1 = player1.getElementalBondsMatchData();
        ElementalBondsMatchData elementalBondsMatchData2 = player2.getElementalBondsMatchData();
        syncGameStart(elementalBondsRoomData, elementalBondsMatchData1, elementalBondsMatchData2);
        syncGameStart(elementalBondsRoomData, elementalBondsMatchData2, elementalBondsMatchData1);

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private void syncGameStart(ElementalBondsRoomData elementalBondsRoomData, ElementalBondsMatchData selfElementalBondsMatchData, ElementalBondsMatchData opponentElementalBondsMatchData) {
        if (!selfElementalBondsMatchData.isAi() && elementalBondsRoomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType() && elementalBondsRoomData.getModeId() != ElementalBondsModeType.PRACTICE.getType()) {
            // 返回客户端
            ElementalBondsMatchResultRespMsg.Builder elementalBondsMatchResultRespMsgBuilder = ElementalBondsMatchResultRespMsg.newBuilder();
            elementalBondsMatchResultRespMsgBuilder.setRet(0);
            elementalBondsMatchResultRespMsgBuilder.setMode(elementalBondsRoomData.getModeId());
            elementalBondsMatchResultRespMsgBuilder.setSceneId(elementalBondsRoomData.getGameData().getSceneId());
            elementalBondsMatchResultRespMsgBuilder.setOpponent(CrossElementalBondsPb.parseElementalBondsOpponentMsg(opponentElementalBondsMatchData));
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_MATCH_RESULT_FROM_CROSS_VALUE, elementalBondsMatchResultRespMsgBuilder, selfElementalBondsMatchData);
        }
    }

    private void timerCheckNpcOperate(ElementalBondsRoomData elementalBondsRoomData) {
        int turn = elementalBondsRoomData.getTurnPlayer();
        ElementalBondsPlayerData turnPlayer = turn == 1 ? elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();
        ElementalBondsPlayerData nextPlayer = turn == 1 ? elementalBondsRoomData.getPlayer2() : elementalBondsRoomData.getPlayer1();
        ElementalBondsMatchData turnMatchData = turnPlayer.getElementalBondsMatchData();
        ElementalBondsMatchData nextMatchData = nextPlayer.getElementalBondsMatchData();

        // 不是AI || AI没步数
        if (!turnMatchData.isAi() || turnPlayer.getStep() <= 0) {
            return;
        }

        // 真人还没进来 && 第一回合 && 剩余时间还有一半以上
        long now = TimeUtils.currentTimeMillis();
        if (!nextMatchData.isSeeBegin() &&
                elementalBondsRoomData.getRound() == 1 &&
                elementalBondsRoomData.getExpireTime() - now > ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(elementalBondsRoomData.getModeId()).getClearTime() / 2) {
            return;
        }

        // 客户端正在播放动画
        if (now < elementalBondsRoomData.getShowFinishTime()) {
            return;
        }

        // 客户端播放完动画后再确定下次操作时间
        if (turnMatchData.getAiOperateTime() < elementalBondsRoomData.getShowFinishTime()) {
            turnMatchData.setAiOperateTime(randomAiOperateWaitTime() + elementalBondsRoomData.getShowFinishTime());
        }

        // 未到AI操作时间
        if (now < turnMatchData.getAiOperateTime()) {
            return;
        }

        // AI操作
        int ret = ElementalBondsAiLogic.aiOperate(this, elementalBondsRoomData, nextMatchData, turnPlayer);
        if (ret != 0) {
            logger.error("elementalBonds timerCheckNpcOperate AI operate move error! ret = {} opponentUserId = {}", ret, turn == 1 ? elementalBondsRoomData.getPlayer2().getElementalBondsMatchData().getPlayerId() : elementalBondsRoomData.getPlayer1().getElementalBondsMatchData().getPlayerId());
        }
    }

    public void checkExpire() {
        for (ElementalBondsRoomData elementalBondsRoomData : roomMap.values()) {
            try {
                synchronized (elementalBondsRoomData) {
                    if (!isRoomPlaying(elementalBondsRoomData)) {
                        continue;
                    }
                    // 检查回合切换
                    checkTurnAndGameOver(null, elementalBondsRoomData, -1L);
                    // npc操作
                    timerCheckNpcOperate(elementalBondsRoomData);
                }
            } catch (Exception e) {
                logger.error("elementalBonds checkExpire error : ", e);
            }
        }
    }

    private int surrender(String key, boolean timesLimit) {
        ElementalBondsMatchData elementalBondsMatchData = roomUserMap.get(key);
        if (elementalBondsMatchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData elementalBondsRoomData = roomMap.get(elementalBondsMatchData.getRoomId());
        if (elementalBondsRoomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        synchronized (elementalBondsRoomData) {
            if (!isRoomPlaying(elementalBondsRoomData)) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_NO_PLAYING;
            }

            if (timesLimit && elementalBondsRoomData.getModeId() != ElementalBondsModeType.PRACTICE.getType()
                    && elementalBondsRoomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_DAY_SURRENDER_LIMIT;
            }

            boolean isPlayer1 = Objects.equals(elementalBondsRoomData.getPlayer1().getElementalBondsMatchData().getKey(), key);

            elementalBondsRoomData.setWinPlayer(isPlayer1 ? 2 : 1);
            elementalBondsRoomData.setWinType(ElementalBondsGameOverType.SURRENDER.getType());
            handleGameOver(null, elementalBondsRoomData, 0L);

            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private void handleGameOver(ElementalBondsNoticeHelper elementalBondsNoticeHelper, ElementalBondsRoomData elementalBondsRoomData, long clientShowNo) {
        logger.info("[handleGameOver] : [roomId : {}, modeId : {}, clientShowNo : {}, serverShowNo : {}, handleTime : {}, showFinishTime : {}, expireTime : {}]",
                elementalBondsRoomData.getRoomId(), elementalBondsRoomData.getModeId(), clientShowNo, elementalBondsRoomData.getShowNo(), TimeUtils.currentTimeMillis(), elementalBondsRoomData.getShowFinishTime(), elementalBondsRoomData.getExpireTime());
        if (elementalBondsRoomData.getWinPlayer() == 0 || elementalBondsRoomData.getWinType() == 0) {
            logger.error("elementalBonds handleGameOver error! game no winner! userId1 {}, userId2 {}.", elementalBondsRoomData.getPlayer1() != null ? elementalBondsRoomData.getPlayer1().getElementalBondsMatchData().getPlayerId() : -1L, elementalBondsRoomData.getPlayer2() != null ? elementalBondsRoomData.getPlayer2().getElementalBondsMatchData().getPlayerId() : -1L);
            return;
        }

        List<ElementalBondsMatchData> elementalBondsMatchDataList = new ArrayList<>();
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer1().getElementalBondsMatchData());
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer2().getElementalBondsMatchData());

        // 同步游戏结束
        for (ElementalBondsMatchData elementalBondsMatchData : elementalBondsMatchDataList) {
            if (!elementalBondsMatchData.isAi()) {
                ElementalBondsSyncNoticeRespMsg.Builder elementalBondsSyncNoticeRespMsgBuilder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                elementalBondsSyncNoticeRespMsgBuilder.setType(ElementalBondsOperateType.GAME_OVER.getType());
                elementalBondsSyncNoticeRespMsgBuilder.setOver(CrossElementalBondsPb.parseElementalBondsGameOverMsg(elementalBondsRoomData, elementalBondsMatchData.getKey()));
                sendNotice(elementalBondsSyncNoticeRespMsgBuilder, elementalBondsMatchData, elementalBondsRoomData.getModeId(), elementalBondsNoticeHelper);
            }
        }

        // 移除房间
        removeRoom(elementalBondsRoomData);
    }

    private void handleTurnChange(ElementalBondsNoticeHelper elementalBondsNoticeHelper, ElementalBondsRoomData elementalBondsRoomData, boolean roundChange, long now,
                                  ElementalBondsPlayerData turnElementalBondsPlayerData, ElementalBondsPlayerData nextElementalBondsPlayerData, ElementalBondsModeConfig elementalBondsModeConfig) {
        int nextTurn = elementalBondsRoomData.getTurnPlayer() == 1 ? 2 : 1;

        // 准备数据，方便消息一起发
        List<ElementalBondsMatchData> elementalBondsMatchDataList = new ArrayList<>();
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer1().getElementalBondsMatchData());
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer2().getElementalBondsMatchData());
        ElementalBondsNoticeHelper tmpElementalBondsNoticeHelper = elementalBondsNoticeHelper;
        if (tmpElementalBondsNoticeHelper == null) {
            tmpElementalBondsNoticeHelper = new ElementalBondsNoticeHelper(elementalBondsRoomData.getModeId());
        }

        // 当前玩家清除清除
        turnElementalBondsPlayerData.setStep(0);
        turnElementalBondsPlayerData.getElementalBondsCardData().turnReset();
        elementalBondsRoomData.getGameData().setScene13Count(0);
        elementalBondsRoomData.getGameData().setScene04Count(0);
        elementalBondsRoomData.getGameData().setScene08Count(0);
        elementalBondsRoomData.getGameData().setScene07Count(0);

        // 清空表现数据
        elementalBondsRoomData.getShowTimeList().clear();
        elementalBondsRoomData.setShowFinishTime(now);
        // 房间数据修改
        elementalBondsRoomData.setExpireTime(now + elementalBondsModeConfig.getClearTime());
        elementalBondsRoomData.setTurnPlayer(nextTurn);
        // 加表现时间
        elementalBondsRoomData.addShowTime(5000);
        // 如果对方掉线，则从头开始计时
        if (nextElementalBondsPlayerData.getElementalBondsMatchData().getDisconnectTime() > 0) {
            nextElementalBondsPlayerData.getElementalBondsMatchData().setDisconnectTime(TimeUtils.currentTimeMillis());
        }

        if (roundChange) {
            // 加回合
            elementalBondsRoomData.addRound();
            // 加步数
            turnElementalBondsPlayerData.setStep(elementalBondsModeConfig.getMaxStep());
            nextElementalBondsPlayerData.setStep(elementalBondsModeConfig.getMaxStep());
        }

        // 回合切换清空效果
        ElementalBondsOperateResult elementalBondsOperateResult1 = new ElementalBondsOperateResult(ElementalBondsOperateType.TURN_PLAYER, false, nextTurn);
        ElementalBondsGameLogic.turnChangeResetBlock(elementalBondsRoomData.getGameData().getBlockList(), elementalBondsOperateResult1);
        ElementalBondsSyncNoticeRespMsg.Builder lastElementalBondsSyncNoticeRespMsgBuilder = ElementalBondsSyncNoticeRespMsg.newBuilder();
        for (ElementalBondsMatchData elementalBondsMatchData : elementalBondsMatchDataList) {
            if (!elementalBondsMatchData.isAi()) {
                lastElementalBondsSyncNoticeRespMsgBuilder.setType(elementalBondsOperateResult1.getElementalBondsOperateType().getType());
                lastElementalBondsSyncNoticeRespMsgBuilder.setOperate(CrossElementalBondsPb.parseElementalBondsOperateResultMsg(elementalBondsOperateResult1, elementalBondsRoomData, elementalBondsMatchData.getKey()));
                sendNotice(lastElementalBondsSyncNoticeRespMsgBuilder, elementalBondsMatchData, elementalBondsRoomData.getModeId(), tmpElementalBondsNoticeHelper);
            }
        }

        // 回合切换操作
        ElementalBondsOperateResult elementalBondsOperateResult2 = ElementalBondsOperateResult.newSceneRefreshResult(nextTurn);
        ElementalBondsSceneLogic.turnChangeRefresh(new Random(), elementalBondsRoomData.getGameData(), elementalBondsOperateResult2);
        for (ElementalBondsMatchData elementalBondsMatchData : elementalBondsMatchDataList) {
            if (!elementalBondsMatchData.isAi()) {
                ElementalBondsSyncNoticeRespMsg.Builder nextElementalBondsSyncNoticeRespMsgBuilder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                nextElementalBondsSyncNoticeRespMsgBuilder.setType(elementalBondsOperateResult2.getElementalBondsOperateType().getType());
                nextElementalBondsSyncNoticeRespMsgBuilder.setOperate(CrossElementalBondsPb.parseElementalBondsOperateResultMsg(elementalBondsOperateResult2, elementalBondsRoomData, elementalBondsMatchData.getKey()));
                sendNotice(nextElementalBondsSyncNoticeRespMsgBuilder, elementalBondsMatchData, elementalBondsRoomData.getModeId(), tmpElementalBondsNoticeHelper);
            }
        }

        // 一起发消息
        if (elementalBondsNoticeHelper == null) {
            sendNoticeList(tmpElementalBondsNoticeHelper);
        }
    }

    private int sendExpression(String key, int expressionId) {
        ElementalBondsMatchData elementalBondsMatchData = roomUserMap.get(key);
        if (elementalBondsMatchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData elementalBondsRoomData = roomMap.get(elementalBondsMatchData.getRoomId());
        if (elementalBondsRoomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        synchronized (elementalBondsRoomData) {
            if (!elementalBondsRoomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_NO_START;
            }
            boolean isPlayer1 = Objects.equals(elementalBondsRoomData.getPlayer1().getElementalBondsMatchData().getKey(), key);
            ElementalBondsPlayerData selfElementalBondsPlayerData = isPlayer1 ? elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();
            ElementalBondsPlayerData opponentElementalBondsPlayerData = isPlayer1 ? elementalBondsRoomData.getPlayer2() : elementalBondsRoomData.getPlayer1();

            // 设置表情发送时间
            long now = TimeUtils.currentTimeMillis();
            if (now - selfElementalBondsPlayerData.getSendExpressionTime() < ElementalBondsParam.getInstance().getExpressionInterval()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_EXPRESSION_FREQUENTLY;
            }
            selfElementalBondsPlayerData.setSendExpressionTime(now);

            // 发送表情
            if (!opponentElementalBondsPlayerData.getElementalBondsMatchData().isAi()) {
                ElementalBondsSyncNoticeRespMsg.Builder elementalBondsSyncNoticeRespMsgBuilder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                elementalBondsSyncNoticeRespMsgBuilder.setType(ElementalBondsOperateType.SEND_EXPRESSION.getType());
                elementalBondsSyncNoticeRespMsgBuilder.setExpressionId(expressionId);
                sendNotice(elementalBondsSyncNoticeRespMsgBuilder, opponentElementalBondsPlayerData.getElementalBondsMatchData(), elementalBondsRoomData.getModeId(), null);
            }

            // 返回
            int code = elementalBondsRoomData.getModeId() == ElementalBondsModeType.PRACTICE.getType() || elementalBondsRoomData.getModeId() == ElementalBondsModeType.FRIEND_PK.getType() ?
                    ElementalBondsPb.ELEMENTAL_BONDS_OPERATE_VALUE : ElementalBondsPb.ELEMENTAL_BONDS_SEND_EXPRESSION_FROM_CROSS_VALUE;
            ElementalBondsOperateRespMsg.Builder elementalBondsOperateRespMsgBuilder = ElementalBondsOperateRespMsg.newBuilder();
            elementalBondsOperateRespMsgBuilder.setRet(0);
            sendGamePacket(code, elementalBondsOperateRespMsgBuilder, selfElementalBondsPlayerData.getElementalBondsMatchData());

            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private int operateUseCard(String key, ElementalBondsCardParam elementalBondsCardParam) {
        ElementalBondsMatchData elementalBondsMatchData = roomUserMap.get(key);
        if (elementalBondsMatchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData elementalBondsRoomData = roomMap.get(elementalBondsMatchData.getRoomId());
        if (elementalBondsRoomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        synchronized (elementalBondsRoomData) {
            if (!elementalBondsRoomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_NO_START;
            }
            boolean isPlayer1 = isPlayer1(elementalBondsRoomData, key);
            if (!turnSelf(elementalBondsRoomData, isPlayer1)) {
                return GameErrorCode.E_ELEMENTAL_BONDS_NO_TURN_SELF;
            }

            // 补充点参数
            elementalBondsCardParam.setSide(isPlayer1 ? 1 : 2);

            ElementalBondsOperateResult elementalBondsOperateResult = ElementalBondsGameLogic.operateUseCard(elementalBondsRoomData, isPlayer1,
                    elementalBondsCardParam, -1, elementalBondsRoomData.getRound());
            if (elementalBondsOperateResult.getRet() != GameErrorCode.E_SUCCESS_VALUE) {
                return elementalBondsOperateResult.getRet();
            }
            handleUseCardResult(isPlayer1, elementalBondsRoomData, elementalBondsOperateResult, null);

            // 日志
            addElementalBondsOperateLog(elementalBondsRoomData, isPlayer1, ElementalBondsOperateLogType.USE_CARD, elementalBondsCardParam.getCardId());

            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private int operateUseSkill(String key, ElementalBondsSkillParam elementalBondsSkillParam) {
        ElementalBondsMatchData elementalBondsMatchData = roomUserMap.get(key);
        if (elementalBondsMatchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData elementalBondsRoomData = roomMap.get(elementalBondsMatchData.getRoomId());
        if (elementalBondsRoomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        synchronized (elementalBondsRoomData) {
            if (!elementalBondsRoomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_NO_START;
            }
            boolean isPlayer1 = isPlayer1(elementalBondsRoomData, key);
            if (!turnSelf(elementalBondsRoomData, isPlayer1)) {
                return GameErrorCode.E_ELEMENTAL_BONDS_NO_TURN_SELF;
            }
            ElementalBondsOperateResult elementalBondsOperateResult = ElementalBondsGameLogic.operateUseSkill(elementalBondsRoomData, isPlayer1,
                    elementalBondsSkillParam, -1, elementalBondsRoomData.getRound());
            if (elementalBondsOperateResult.getRet() != GameErrorCode.E_SUCCESS_VALUE) {
                return elementalBondsOperateResult.getRet();
            }
            handleUseSkillResult(isPlayer1, elementalBondsRoomData, elementalBondsOperateResult, null);

            // 日志
            addElementalBondsOperateLog(elementalBondsRoomData, isPlayer1, ElementalBondsOperateLogType.USE_SKILL, elementalBondsSkillParam.getSkillId());

            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private int operateMove(String key, ElementalBondsMoveParam elementalBondsMoveParam) {
        ElementalBondsMatchData elementalBondsMatchData = roomUserMap.get(key);
        if (elementalBondsMatchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData elementalBondsRoomData = roomMap.get(elementalBondsMatchData.getRoomId());
        if (elementalBondsRoomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        synchronized (elementalBondsRoomData) {
            if (!elementalBondsRoomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_NO_START;
            }
            boolean isPlayer1 = isPlayer1(elementalBondsRoomData, key);
            if (!turnSelf(elementalBondsRoomData, isPlayer1)) {
                return GameErrorCode.E_ELEMENTAL_BONDS_NO_TURN_SELF;
            }
            ElementalBondsOperateResult elementalBondsOperateResult = ElementalBondsGameLogic.operateMove(elementalBondsRoomData, isPlayer1,
                    elementalBondsMoveParam, -1, elementalBondsRoomData.getRound());
            if (elementalBondsOperateResult.getRet() != GameErrorCode.E_SUCCESS_VALUE) {
                return elementalBondsOperateResult.getRet();
            }
            handleMoveResult(isPlayer1, elementalBondsRoomData, elementalBondsOperateResult, null);

            // 日志
            addElementalBondsOperateLog(elementalBondsRoomData, isPlayer1, ElementalBondsOperateLogType.MOVE_BLOCK, 0);

            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    public int operateNpcMove(ElementalBondsRoomData elementalBondsRoomData, ElementalBondsMoveParam elementalBondsMoveParam, ElementalBondsPlayerData turnPlayer) {
        boolean isPlayer1 = elementalBondsRoomData.getPlayer1() == turnPlayer;
        if (!turnSelf(elementalBondsRoomData, isPlayer1)) {
            return GameErrorCode.E_ELEMENTAL_BONDS_NO_TURN_SELF;
        }

        ElementalBondsOperateResult elementalBondsOperateResult = ElementalBondsGameLogic.operateMove(elementalBondsRoomData, isPlayer1,
                elementalBondsMoveParam, -1, elementalBondsRoomData.getRound());
        if (elementalBondsOperateResult.getRet() != GameErrorCode.E_SUCCESS_VALUE) {
            return elementalBondsOperateResult.getRet();
        }
        handleMoveResult(isPlayer1, elementalBondsRoomData, elementalBondsOperateResult, null);

        // 日志
        //addElementalBondsOperateLog(elementalBondsRoomData, isPlayer1, ElementalBondsOperateLogType.MOVE_BLOCK, 0);

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    public int operateNpcUseCard(ElementalBondsRoomData elementalBondsRoomData, ElementalBondsCardParam elementalBondsCardParam, ElementalBondsPlayerData turnPlayer) {
        boolean isPlayer1 = elementalBondsRoomData.getPlayer1() == turnPlayer;
        if (!turnSelf(elementalBondsRoomData, isPlayer1)) {
            return GameErrorCode.E_ELEMENTAL_BONDS_NO_TURN_SELF;
        }

        ElementalBondsOperateResult elementalBondsOperateResult = ElementalBondsGameLogic.operateUseCard(elementalBondsRoomData, isPlayer1,
                elementalBondsCardParam, -1, elementalBondsRoomData.getRound());
        if (elementalBondsOperateResult.getRet() != GameErrorCode.E_SUCCESS_VALUE) {
            return elementalBondsOperateResult.getRet();
        }
        handleUseCardResult(isPlayer1, elementalBondsRoomData, elementalBondsOperateResult, null);

        // 日志
        addElementalBondsOperateLog(elementalBondsRoomData, isPlayer1, ElementalBondsOperateLogType.USE_CARD, elementalBondsCardParam.getCardId());

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    public int operateNpcUseSkill(ElementalBondsRoomData elementalBondsRoomData, ElementalBondsSkillParam elementalBondsSkillParam, ElementalBondsPlayerData turnPlayer) {
        boolean isPlayer1 = elementalBondsRoomData.getPlayer1() == turnPlayer;
        if (!turnSelf(elementalBondsRoomData, isPlayer1)) {
            return GameErrorCode.E_ELEMENTAL_BONDS_NO_TURN_SELF;
        }

        ElementalBondsOperateResult elementalBondsOperateResult = ElementalBondsGameLogic.operateUseSkill(elementalBondsRoomData, isPlayer1,
                elementalBondsSkillParam, -1, elementalBondsRoomData.getRound());
        if (elementalBondsOperateResult.getRet() != GameErrorCode.E_SUCCESS_VALUE) {
            return elementalBondsOperateResult.getRet();
        }
        handleUseSkillResult(isPlayer1, elementalBondsRoomData, elementalBondsOperateResult, null);

        // 日志
        addElementalBondsOperateLog(elementalBondsRoomData, isPlayer1, ElementalBondsOperateLogType.USE_SKILL, elementalBondsSkillParam.getSkillId());

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private boolean turnSelf(ElementalBondsRoomData elementalBondsRoomData, boolean isPlayer1) {
        if ((isPlayer1 ? 1 : 2) != elementalBondsRoomData.getTurnPlayer()) {
            return false; // 不是轮到自己
        }
        ElementalBondsPlayerData elementalBondsPlayerData = isPlayer1 ? elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();
        return elementalBondsPlayerData.getStep() > 0; // 自己回合但是步数用完了（过会儿定时器就切换过去了）
    }

    private boolean isPlayer1(ElementalBondsRoomData elementalBondsRoomData, String key) {
        ElementalBondsPlayerData elementalBondsPlayerData = elementalBondsRoomData.getPlayer1();
        if (elementalBondsPlayerData == null) {
            return false;
        }
        return Objects.equals(elementalBondsPlayerData.getElementalBondsMatchData().getKey(), key);
    }

    private void handleMoveResult(boolean isPlayer1, ElementalBondsRoomData elementalBondsRoomData,
                                  ElementalBondsOperateResult elementalBondsOperateResult, ElementalBondsNoticeHelper elementalBondsNoticeHelper) {
        ElementalBondsModeConfig elementalBondsModeConfig = ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(elementalBondsRoomData.getModeId());
        ElementalBondsPlayerData elementalBondsPlayerData = isPlayer1 ? elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();

        int redColor = isPlayer1 ? ElementalBondsColorType.RED.getType() : ElementalBondsColorType.BLUE.getType();
        int redCount = 0;
        int blueColor = isPlayer1 ? ElementalBondsColorType.BLUE.getType() : ElementalBondsColorType.RED.getType();
        int blueCount = 0;

        // 统计结果
        int scene07AddMp = elementalBondsOperateResult.getScene07AddMp();
        long addScore = 0;
        int addStep = elementalBondsOperateResult.getBombAddStepStatus() == 1 ? 1 : 0;
        for (ElementalBondsOperateShow elementalBondsOperateShow : elementalBondsOperateResult.getElementalBondsOperateShowList()) {
            for (ElementalBondsOperateRoundResult elementalBondsOperateRoundResult : elementalBondsOperateShow.getElementalBondsOperateRoundResultList()) {
                for (ElementalBondsOperateStageResult elementalBondsOperateStageResult : elementalBondsOperateRoundResult.getStageResultList()) {
                    for (ElementalBondsBlock elementalBondsBlock : elementalBondsOperateStageResult.getEliminateBlockList()) {
                        if (elementalBondsBlock.getElementalBondsBlockType().getBigType() != ElementalBondsBlockBigType.FRAME) {
                            if (elementalBondsBlock.getColor() == redColor) {
                                scene07AddMp += 1;
                                elementalBondsBlock.setSp(1);
                                redCount++;
                            }
                            if (elementalBondsBlock.getColor() == blueColor) {
                                blueCount++;
                            }
                            elementalBondsBlock.setScore(calcBlockScore(elementalBondsBlock));
                            addScore += elementalBondsBlock.getScore();
                        }
                    }
                    for (ElementalBondsBlock elementalBondsBlock : elementalBondsOperateStageResult.getAddStepBlockList()) {
                        if (elementalBondsBlock.getElementalBondsBlockType() == ElementalBondsBlockType.DIAN_SHAN_LEI_MING) {
                            addStep += ElementalBondsSceneLogic.scene_09_getAddStepNum();
                        }
                    }
                }
            }
        }

        // 修改数据
        elementalBondsPlayerData.reduceStep();
        elementalBondsPlayerData.addScore(addScore);
        elementalBondsPlayerData.addAchieveStatistical(ElementalBondsHonorMomentType.OneGameEliminateBlueCount, blueCount);
        elementalBondsPlayerData.addAchieveStatistical(ElementalBondsHonorMomentType.OneGameEliminateRedCount, redCount);
        elementalBondsPlayerData.getElementalBondsCardData().addMp(scene07AddMp);
        elementalBondsRoomData.setGameData(elementalBondsOperateResult.getElementalBondsGameData());
        elementalBondsPlayerData.addStep(addStep, elementalBondsModeConfig.getMaxStep());

        // 同步操作结果
        elementalBondsRoomData.addShowTime(20000);
        List<ElementalBondsMatchData> elementalBondsMatchDataList = new ArrayList<>();
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer1().getElementalBondsMatchData());
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer2().getElementalBondsMatchData());
        for (ElementalBondsMatchData elementalBondsMatchData : elementalBondsMatchDataList) {
            if (!elementalBondsMatchData.isAi()) {
                ElementalBondsSyncNoticeRespMsg.Builder elementalBondsSyncNoticeRespMsg = ElementalBondsSyncNoticeRespMsg.newBuilder();
                elementalBondsSyncNoticeRespMsg.setType(ElementalBondsOperateType.MOVE.getType());
                elementalBondsSyncNoticeRespMsg.setOperate(CrossElementalBondsPb.parseElementalBondsOperateResultMsg(elementalBondsOperateResult, elementalBondsRoomData, elementalBondsMatchData.getKey()));
                sendNotice(elementalBondsSyncNoticeRespMsg, elementalBondsMatchData, elementalBondsRoomData.getModeId(), elementalBondsNoticeHelper);
            }
        }
    }

    private void handleUseCardResult(boolean isPlayer1, ElementalBondsRoomData elementalBondsRoomData,
                                     ElementalBondsOperateResult elementalBondsOperateResult, ElementalBondsNoticeHelper elementalBondsNoticeHelper) {
        ElementalBondsModeConfig elementalBondsModeConfig = ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(elementalBondsRoomData.getModeId());
        ElementalBondsPlayerData elementalBondsPlayerData = isPlayer1 ? elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();

        int redColor = isPlayer1 ? ElementalBondsColorType.RED.getType() : ElementalBondsColorType.BLUE.getType();
        int redCount = 0;
        int blueColor = isPlayer1 ? ElementalBondsColorType.BLUE.getType() : ElementalBondsColorType.RED.getType();
        int blueCount = 0;

        // 统计结果
        int scene07AddMp = elementalBondsOperateResult.getScene07AddMp();
        long addScore = 0;
        int addStep = elementalBondsOperateResult.getBombAddStepStatus() == 1 ? 1 : 0;
        for (ElementalBondsOperateShow elementalBondsOperateShow : elementalBondsOperateResult.getElementalBondsOperateShowList()) {
            for (ElementalBondsOperateRoundResult elementalBondsOperateRoundResult : elementalBondsOperateShow.getElementalBondsOperateRoundResultList()) {
                for (ElementalBondsOperateStageResult elementalBondsOperateStageResult : elementalBondsOperateRoundResult.getStageResultList()) {
                    for (ElementalBondsBlock elementalBondsBlock : elementalBondsOperateStageResult.getEliminateBlockList()) {
                        if (elementalBondsBlock.getElementalBondsBlockType().getBigType() != ElementalBondsBlockBigType.FRAME) {
                            elementalBondsBlock.setScore(calcBlockScore(elementalBondsBlock));
                            addScore += elementalBondsBlock.getScore();
                            if (elementalBondsBlock.getColor() == redColor) {
                                redCount++;
                            }
                            if (elementalBondsBlock.getColor() == blueColor) {
                                blueCount ++;
                            }
                        }
                    }
                    for (ElementalBondsBlock elementalBondsBlock : elementalBondsOperateStageResult.getAddStepBlockList()) {
                        if (elementalBondsBlock.getElementalBondsBlockType() == ElementalBondsBlockType.DIAN_SHAN_LEI_MING) {
                            addStep += ElementalBondsSceneLogic.scene_09_getAddStepNum();
                        }
                    }
                }
            }
        }

        // 修改数据
        ElementalBondsCardData elementalBondsCardData = elementalBondsPlayerData.getElementalBondsCardData();
        elementalBondsPlayerData.addScore(addScore);
        elementalBondsPlayerData.addAchieveStatistical(ElementalBondsHonorMomentType.OneGameUseCardTimes, 1L);
        elementalBondsPlayerData.addAchieveStatistical(ElementalBondsHonorMomentType.OneGameEliminateBlueCount, blueCount);
        elementalBondsPlayerData.addAchieveStatistical(ElementalBondsHonorMomentType.OneGameEliminateRedCount, redCount);
        if (elementalBondsPlayerData.getElementalBondsMatchData().isAi()) {
            elementalBondsCardData.setCardMp(0);
        }
        elementalBondsPlayerData.getElementalBondsCardData().addMp(scene07AddMp);
        elementalBondsRoomData.setGameData(elementalBondsOperateResult.getElementalBondsGameData());
        elementalBondsPlayerData.addStep(addStep, elementalBondsModeConfig.getMaxStep());

        // 同步操作结果
        elementalBondsRoomData.addShowTime(ElementalBondsConfigMgr.getInstance().getCardConfigMap().get(elementalBondsCardData.getCardId()).getMaxTime());
        List<ElementalBondsMatchData> elementalBondsMatchDataList = new ArrayList<>();
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer1().getElementalBondsMatchData());
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer2().getElementalBondsMatchData());
        for (ElementalBondsMatchData elementalBondsMatchData : elementalBondsMatchDataList) {
            if (!elementalBondsMatchData.isAi()) {
                ElementalBondsSyncNoticeRespMsg.Builder elementalBondsSyncNoticeRespMsg = ElementalBondsSyncNoticeRespMsg.newBuilder();
                elementalBondsSyncNoticeRespMsg.setType(ElementalBondsOperateType.USE_CARD.getType());
                elementalBondsSyncNoticeRespMsg.setOperate(CrossElementalBondsPb.parseElementalBondsOperateResultMsg(elementalBondsOperateResult, elementalBondsRoomData, elementalBondsMatchData.getKey()));
                sendNotice(elementalBondsSyncNoticeRespMsg, elementalBondsMatchData, elementalBondsRoomData.getModeId(), elementalBondsNoticeHelper);
            }
        }
    }

    private void handleUseSkillResult(boolean isPlayer1, ElementalBondsRoomData elementalBondsRoomData,
                                      ElementalBondsOperateResult elementalBondsOperateResult, ElementalBondsNoticeHelper elementalBondsNoticeHelper) {
        ElementalBondsModeConfig elementalBondsModeConfig = ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(elementalBondsRoomData.getModeId());
        ElementalBondsPlayerData elementalBondsPlayerData = isPlayer1 ? elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();
        int redColor = isPlayer1 ? ElementalBondsColorType.RED.getType() : ElementalBondsColorType.BLUE.getType();
        int redCount = 0;
        int blueColor = isPlayer1 ? ElementalBondsColorType.BLUE.getType() : ElementalBondsColorType.RED.getType();
        int blueCount = 0;

        // 统计结果
        int scene07AddMp = elementalBondsOperateResult.getScene07AddMp();
        long addScore = 0;
        int addStep = elementalBondsOperateResult.getBombAddStepStatus() == 1 ? 1 : 0;
        boolean useSkill_1003 = elementalBondsOperateResult.getSkillId() == ElementalBondsSkillType.SKILL_1003.getType();
        for (ElementalBondsOperateShow elementalBondsOperateShow : elementalBondsOperateResult.getElementalBondsOperateShowList()) {
            for (ElementalBondsOperateRoundResult elementalBondsOperateRoundResult : elementalBondsOperateShow.getElementalBondsOperateRoundResultList()) {
                for (ElementalBondsOperateStageResult elementalBondsOperateStageResult : elementalBondsOperateRoundResult.getStageResultList()) {
                    for (ElementalBondsBlock elementalBondsBlock : elementalBondsOperateStageResult.getEliminateBlockList()) {
                        if (elementalBondsBlock.getElementalBondsBlockType().getBigType() != ElementalBondsBlockBigType.FRAME) {
                            if (useSkill_1003 || elementalBondsBlock.getColor() == redColor) {
                                scene07AddMp += 1;
                                elementalBondsBlock.setSp(1);
                            }
                            if (elementalBondsBlock.getColor() == redColor) {
                                redCount++;
                            }
                            if (elementalBondsBlock.getColor() == blueColor) {
                                blueCount++;
                            }
                            elementalBondsBlock.setScore(calcBlockScore(elementalBondsBlock));
                            addScore += elementalBondsBlock.getScore();
                        }
                    }
                    for (ElementalBondsBlock elementalBondsBlock : elementalBondsOperateStageResult.getAddStepBlockList()) {
                        if (elementalBondsBlock.getElementalBondsBlockType() == ElementalBondsBlockType.DIAN_SHAN_LEI_MING) {
                            addStep += ElementalBondsSceneLogic.scene_09_getAddStepNum();
                        }
                    }
                }
            }
        }

        // 修改数据
        int skillId = elementalBondsOperateResult.getSkillId();
        for (ElementalBondsSkillData elementalBondsSkillData : elementalBondsPlayerData.getElementalBondsSkillDataList()) {
            if (elementalBondsSkillData.getSkillId() == skillId) {
                elementalBondsSkillData.reduceTimes();
            }
        }
        elementalBondsPlayerData.addScore(addScore);
        elementalBondsPlayerData.addAchieveStatistical(ElementalBondsHonorMomentType.OneGameEliminateBlueCount, blueCount);
        elementalBondsPlayerData.addAchieveStatistical(ElementalBondsHonorMomentType.OneGameEliminateRedCount, redCount);
        elementalBondsPlayerData.getElementalBondsCardData().addMp(scene07AddMp);
        elementalBondsRoomData.setGameData(elementalBondsOperateResult.getElementalBondsGameData());
        // 先扣步数再加步数
        if (useSkill_1003) {
            elementalBondsPlayerData.reduceStep();
        }
        elementalBondsPlayerData.addStep(addStep, elementalBondsModeConfig.getMaxStep());

        // 同步操作结果
        elementalBondsRoomData.addShowTime(ElementalBondsConfigMgr.getInstance().getSkillConfigMap().get(skillId).getMaxTime());
        List<ElementalBondsMatchData> elementalBondsMatchDataList = new ArrayList<>();
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer1().getElementalBondsMatchData());
        elementalBondsMatchDataList.add(elementalBondsRoomData.getPlayer2().getElementalBondsMatchData());
        for (ElementalBondsMatchData elementalBondsMatchData : elementalBondsMatchDataList) {
            if (!elementalBondsMatchData.isAi()) {
                ElementalBondsSyncNoticeRespMsg.Builder elementalBondsSyncNoticeRespMsg = ElementalBondsSyncNoticeRespMsg.newBuilder();
                elementalBondsSyncNoticeRespMsg.setType(ElementalBondsOperateType.USE_SKILL.getType());
                elementalBondsSyncNoticeRespMsg.setOperate(CrossElementalBondsPb.parseElementalBondsOperateResultMsg(elementalBondsOperateResult, elementalBondsRoomData, elementalBondsMatchData.getKey()));
                sendNotice(elementalBondsSyncNoticeRespMsg, elementalBondsMatchData, elementalBondsRoomData.getModeId(), elementalBondsNoticeHelper);
            }
        }
    }

    private void checkTurnAndGameOver(ElementalBondsNoticeHelper elementalBondsNoticeHelper, ElementalBondsRoomData elementalBondsRoomData, long clientShowNo) {
        if (!isRoomPlaying(elementalBondsRoomData)) {
            return;
        }

        boolean turnChange = false;
        boolean roundChange = false;
        boolean gameOver = false;

        long now = TimeUtils.currentTimeMillis();
        ElementalBondsPlayerData turnElementalBondsPlayerData = elementalBondsRoomData.getTurnPlayer() == 1 ?
                elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();
        ElementalBondsPlayerData nextElementalBondsPlayerData = elementalBondsRoomData.getTurnPlayer() == 1 ?
                elementalBondsRoomData.getPlayer2() : elementalBondsRoomData.getPlayer1();

        // 【检查对手切换】
        ElementalBondsModeConfig elementalBondsModeConfig = ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(elementalBondsRoomData.getModeId());
        ElementalBondsMatchData turnElementalBondsMatchData = turnElementalBondsPlayerData.getElementalBondsMatchData();
        if (turnElementalBondsMatchData.getDisconnectTime() <= 0) { // 操作方没掉线时才能切换
            if (elementalBondsModeConfig.getClearTime() != 0 && now > elementalBondsRoomData.getExpireTime()
                    || turnElementalBondsPlayerData.getStep() <= 0 && now > elementalBondsRoomData.getShowFinishTime()) {
                turnChange = true; // 超时 || 步数用光且表现结束
            }
        }

        if (turnChange) {
            // 【检查回合切换】
            if (nextElementalBondsPlayerData.getStep() <= 0) {
                roundChange = true; // 切换对手，对手也没步数
            }
        }

        if (roundChange) {
            // 【检查游戏正常结束】
            if (elementalBondsRoomData.getRound() >= elementalBondsModeConfig.getMaxRound()
                    && turnElementalBondsPlayerData.getScore() != nextElementalBondsPlayerData.getScore()) {
                gameOver = true; // 回合切换时达到最大回合，且双方分数不同
                if (elementalBondsRoomData.getWinPlayer() == 0) {
                    elementalBondsRoomData.setWinPlayer(elementalBondsRoomData.getPlayer1().getScore() > elementalBondsRoomData.getPlayer2().getScore() ? 1 : 2);
                    elementalBondsRoomData.setWinType(ElementalBondsGameOverType.NORMAL.getType());
                }
            }
        }

        // 【检查游戏掉线结束】
        if (!gameOver) {
            long disconnectTime = turnElementalBondsMatchData.getDisconnectTime();
            if (disconnectTime > 0) {
                long failTime = disconnectTime + ElementalBondsParam.getInstance().getDisconnFailTime(turnElementalBondsMatchData.getDisconnectTimes());
                if (TimeUtils.currentTimeMillis() > failTime) {
                    gameOver = true; // 当前玩家掉线超过一定时间
                    elementalBondsRoomData.setWinPlayer(elementalBondsRoomData.getTurnPlayer() == 1 ? 2 : 1);
                    elementalBondsRoomData.setWinType(ElementalBondsGameOverType.DISCONNECT.getType());
                }
            }
        }

        if (gameOver) {
            // 处理游戏结果
            handleGameOver(elementalBondsNoticeHelper, elementalBondsRoomData, clientShowNo);
        } else if (turnChange) {
            // 处理对手/回合切换
            handleTurnChange(elementalBondsNoticeHelper, elementalBondsRoomData, roundChange, now, turnElementalBondsPlayerData, nextElementalBondsPlayerData, elementalBondsModeConfig);
            // 检查掉线消息
            checkSendDisconnect(elementalBondsNoticeHelper, elementalBondsRoomData);
        }
    }

    private void checkSendDisconnect(ElementalBondsNoticeHelper elementalBondsNoticeHelper, ElementalBondsRoomData elementalBondsRoomData) {
        int turnPlayer = elementalBondsRoomData.getTurnPlayer();
        ElementalBondsPlayerData turnElementalBondsPlayerData = turnPlayer == 1 ? elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();
        ElementalBondsPlayerData nextElementalBondsPlayerData = turnPlayer == 1 ? elementalBondsRoomData.getPlayer2() : elementalBondsRoomData.getPlayer1();

        ElementalBondsMatchData turnElementalBondsMatchData = turnElementalBondsPlayerData.getElementalBondsMatchData();
        ElementalBondsMatchData nextElementalBondsMatchData = nextElementalBondsPlayerData.getElementalBondsMatchData();
        long disconnectTime = turnElementalBondsMatchData.getDisconnectTime();
        if (disconnectTime > 0L && !nextElementalBondsMatchData.isAi()) {
            long failTime = disconnectTime + ElementalBondsParam.getInstance().getDisconnFailTime(turnElementalBondsMatchData.getDisconnectTimes()); // 掉线失败时间
            ElementalBondsSyncNoticeRespMsg.Builder elementalBondsSyncNoticeRespMsgBuilder = ElementalBondsSyncNoticeRespMsg.newBuilder();
            elementalBondsSyncNoticeRespMsgBuilder.setType(ElementalBondsOperateType.OPPONENT_DISCONNECT.getType());
            elementalBondsSyncNoticeRespMsgBuilder.setDropLineWaitTime(failTime);
            sendNotice(elementalBondsSyncNoticeRespMsgBuilder, nextElementalBondsMatchData, elementalBondsRoomData.getModeId(), elementalBondsNoticeHelper);
        }
    }

    private void sendGamePacket(int code, AbstractMessage.Builder<?> messageBuilder, ElementalBondsMatchData elementalBondsMatchData) {
        if (elementalBondsMatchData == null || elementalBondsMatchData.isAi()) {
            return;
        }
        // todo 发消息 要跨域预留一下
        YanQuMessage message = YanQuMessageUtils.buildMessage(code, messageBuilder);
        CrossAgentLinkedControl.send(elementalBondsMatchData.getServerId(), elementalBondsMatchData.getPlayerId(), message);
    }

    private void sendGamePacket(int code, AbstractMessage.Builder<?> messageBuilder, ChannelElementalBondsUserKeyMsg userKey) {
        YanQuMessage message = YanQuMessageUtils.buildMessage(code, messageBuilder);
        CrossAgentLinkedControl.send(userKey.getServerId(), userKey.getPlayerId(), message);
    }

    private void sendGamePacket(int code, AbstractMessage.Builder<?> messageBuilder, long serverId, long playerId) {
        YanQuMessage message = YanQuMessageUtils.buildMessage(code, messageBuilder);
        if (playerId == 0) {
            CrossAgentLinkedControl.send(serverId, message);
        } else {
            CrossAgentLinkedControl.send(serverId, playerId, message);
        }
    }

    public void sendNoticeList(ElementalBondsNoticeHelper elementalBondsNoticeHelper) {
        for (Map.Entry<ElementalBondsMatchData, List<ElementalBondsSyncNoticeRespMsg>> matchNoticeListMapEntry : elementalBondsNoticeHelper.getMatchNoticeListMap().entrySet()) {
            ElementalBondsMatchData elementalBondsMatchData = matchNoticeListMapEntry.getKey();
            if (elementalBondsMatchData == null || elementalBondsMatchData.isAi()) {
                continue;
            }

            List<ElementalBondsSyncNoticeRespMsg> elementalBondsSyncNoticeRespMsgList = matchNoticeListMapEntry.getValue();
            if (elementalBondsSyncNoticeRespMsgList.isEmpty()) {
                continue;
            }

            ElementalBondsBatchSyncNoticeRespMsg.Builder elementalBondsBatchSyncNoticeRespMsgBuilder = ElementalBondsBatchSyncNoticeRespMsg.newBuilder();
            elementalBondsBatchSyncNoticeRespMsgBuilder.setRet(0);
            elementalBondsBatchSyncNoticeRespMsgBuilder.addAllMsgList(elementalBondsSyncNoticeRespMsgList);
            elementalBondsBatchSyncNoticeRespMsgBuilder.setModeId(elementalBondsNoticeHelper.getModeId());

            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_NOTICE_FROM_CROSS_VALUE, elementalBondsBatchSyncNoticeRespMsgBuilder, elementalBondsMatchData);
        }
    }

    public void sendNotice(ElementalBondsSyncNoticeRespMsg.Builder elementalBondsSyncNoticeRespMsg, ElementalBondsMatchData elementalBondsMatchData,
                           int modeId, ElementalBondsNoticeHelper elementalBondsNoticeHelper) {
        if (elementalBondsMatchData == null || elementalBondsMatchData.isAi()) {
            return;
        }

        if (elementalBondsNoticeHelper != null) {
            elementalBondsNoticeHelper.addNotice(elementalBondsSyncNoticeRespMsg, elementalBondsMatchData);
            return;
        }

        ElementalBondsBatchSyncNoticeRespMsg.Builder elementalBondsBatchSyncNoticeRespMsgBuilder = ElementalBondsBatchSyncNoticeRespMsg.newBuilder();
        elementalBondsBatchSyncNoticeRespMsgBuilder.setRet(0);
        elementalBondsBatchSyncNoticeRespMsgBuilder.addMsgList(elementalBondsSyncNoticeRespMsg.build());
        elementalBondsBatchSyncNoticeRespMsgBuilder.setModeId(modeId);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_NOTICE_FROM_CROSS_VALUE, elementalBondsBatchSyncNoticeRespMsgBuilder, elementalBondsMatchData);
    }

    private int calcBlockScore(ElementalBondsBlock elementalBondsBlock) {
        // 基础分
        int score = elementalBondsBlock.getElementalBondsBlockType().getBigType() != ElementalBondsBlockBigType.FRAME ? ElementalBondsParam.getInstance().getBlockScore() : 0;
        // 额外加分
        int multi = 1;
        for (ElementalBondsBlockEffect elementalBondsBlockEffect : elementalBondsBlock.getElementalBondsBlockEffectList()) {
            if (elementalBondsBlockEffect.getEffectType() == ElementalBondsBlockEffectType.SKILL_7) {
                score += (elementalBondsBlockEffect.getEffectValue() - 1);
            } else if (elementalBondsBlockEffect.getEffectType() == ElementalBondsBlockEffectType.ADD_SCORE) {
                score += elementalBondsBlockEffect.getEffectValue();
            } else if (elementalBondsBlockEffect.getEffectType() == ElementalBondsBlockEffectType.WU_CAI_BIN_FEN) {
                score += (elementalBondsBlockEffect.getEffectValue() - 1);
            } else if (elementalBondsBlockEffect.getEffectType() == ElementalBondsBlockEffectType.MULTI_SCORE_HIDE) {
                multi *= elementalBondsBlockEffect.getEffectValue();
            } else if (elementalBondsBlockEffect.getEffectType() == ElementalBondsBlockEffectType.ADD_SCORE_HIDE) {
                multi += elementalBondsBlockEffect.getEffectValue();
            }
        }
        return score * multi;
    }

    private long randomAiOperateWaitTime() {
        List<Integer> aiOperateTimeList = ElementalBondsParam.getInstance().getAiOperateTimeList();
        int waitTime;
        synchronized (needLockRandom) {
            waitTime = needLockRandom.nextInt(aiOperateTimeList.get(1) - aiOperateTimeList.get(0) + 1) + // 这里记得加1，不然配置固定1000毫秒，0就报错了
                    aiOperateTimeList.get(0);
        }
        return waitTime;
    }

    public int getStatusErrorCode(String key) {
        ElementalBondsMatchData elementalBondsMatchData;

        // 匹配中
        elementalBondsMatchData = matchingUserMap.get(key);
        if (elementalBondsMatchData != null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_STATUS_MATCHING;
        }

        elementalBondsMatchData = roomUserMap.get(key);
        if (elementalBondsMatchData != null) {
            ElementalBondsRoomData elementalBondsRoomData = roomMap.get(elementalBondsMatchData.getRoomId());
            if (elementalBondsRoomData != null) {
                // 好友对战中
                if (elementalBondsRoomData.getModeId() == ElementalBondsModeType.FRIEND_PK.getType()) {
                    return GameErrorCode.E_ELEMENTAL_BONDS_STATUS_FRIEND_PK;
                }
                // 练习模式中
                if (elementalBondsRoomData.getModeId() == ElementalBondsModeType.PRACTICE.getType()) {
                    return GameErrorCode.E_ELEMENTAL_BONDS_STATUS_PRACTICE;
                }
                // pvp中
                return GameErrorCode.E_ELEMENTAL_BONDS_STATUS_PVP;
            }
        }
        return GameErrorCode.E_ELEMENTAL_BONDS_STATUS_ERROR;
    }

    private int getNoStatusErrorCode() {
        return GameErrorCode.E_ELEMENTAL_BONDS_STATUS_GAME_END;
    }

    private int showFinish(String key, long showNo) {
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }

        synchronized (roomData) {

            if (!isRoomPlaying(roomData)) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_NO_START;
            }
            boolean isPlayer1 = isPlayer1(roomData, key);

            boolean isFirst = (roomData.getModeId() == ElementalBondsModeType.GUIDE.getType() || matchData.isFriendGuide()) && !matchData.isSeeBegin();
            boolean isSeeBegin = matchData.isSeeBegin();

            // 先修改首次状态
            if (isFirst && roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType())
                roomData.setModeId(ElementalBondsModeType.NEW_HAND_PVP.getType()); // 避免引导中断卡死
            if (!isSeeBegin) {
                matchData.setSeeBegin(true); // 避免重复开入场回合切换

                // 日志
                addElementalBondsOperateLog(roomData, isPlayer1, ElementalBondsOperateLogType.ENTER_ROOM, 0);
            }

            // 处理首次事件
            if (isFirst) {
                handleGuide_First(matchData, roomData, ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(roomData.getModeId())); // 开始引导
            } else if (!isSeeBegin) {
                ElementalBondsSyncNoticeRespMsg.Builder builder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                builder.setType(ElementalBondsOperateType.TURN_PLAYER.getType());
                builder.setOperate(ElementalBondsOperateResultMsg.newBuilder().setSide(roomData.getTurnPlayer()).setSkillId(0).setGameData(CrossElementalBondsPb.parseElementalBondsRoomDataMsg(roomData, matchData.getKey())));
                sendNotice(builder, matchData, roomData.getModeId(), null);
            }

            // 判断动画速度按谁的来
            int turn = isPlayer1 ? 1 : 2;
            ElementalBondsMatchData opponent = isPlayer1 ? roomData.getPlayer2().getElementalBondsMatchData() : roomData.getPlayer1().getElementalBondsMatchData();
            if (turn != roomData.getTurnPlayer() && !opponent.isAi()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_NO_TURN_SELF;
            }

            // 动画耗时修改
            boolean needSync = roomData.showFinish(showNo);
            if (needSync) {
                ElementalBondsNoticeHelper helper = new ElementalBondsNoticeHelper(roomData.getModeId());
                List<ElementalBondsMatchData> list = new ArrayList<>();
                list.add(roomData.getPlayer1().getElementalBondsMatchData());
                list.add(roomData.getPlayer2().getElementalBondsMatchData());
                for (ElementalBondsMatchData userMatchData : list) {
                    if (!userMatchData.isAi()) {
                        ElementalBondsSyncNoticeRespMsg.Builder builder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                        builder.setType(ElementalBondsOperateType.SYNC_EXPIRE_TIME.getType());
                        builder.setExpireTime(roomData.getExpireTime());
                        sendNotice(builder, userMatchData, roomData.getModeId(), helper); // 表现完成同步超时时间
                    }
                }
                checkTurnAndGameOver(helper, roomData, showNo); // 同步回合变化
                sendNoticeList(helper); // 发送
            }
        }
        return GameErrorCode.E_SUCCESS_VALUE;
    }

    // 根据返回值和表现编号，判断是否需要让客户端刷新数据
    private ElementalBondsRoomDataMsg.Builder checkRetRoomData(int ret, String userKey, long showNo) {
        if (ret == 0 || showNo < 0) {
            return null;
        }
        ElementalBondsMatchData matchData = roomUserMap.get(userKey);
        if (matchData != null) {
            ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
            if (roomData != null && roomData.isBegin() && showNo < roomData.getShowNo()) {
                return CrossElementalBondsPb.parseElementalBondsRoomDataMsg(roomData, userKey);
            }
        }
        return null;
    }
    // --------------------------- 盘面机制 --------------------------------

    public void checkRefreshSceneTimer() {
        checkRefreshScene(false);
    }
    public void checkRefreshScene(boolean force) {
        List<Integer> sceneData = elementBondsSystemInfo.getSceneList();
        synchronized (sceneData) {
            long now = TimeUtils.currentTimeMillis();
            if (!force) {
                if (DateHelper.getZeroTimeStamp(now) <= elementBondsSystemInfo.getSceneRefreshTime()) {
                    return;
                }
            }

           ConcurrentHashMap<Integer, Integer> sceneWeightMap = elementBondsSystemInfo.getSceneWeightMap();

            // 拿可选盘面机制和权重
            List<Integer> sceneIdList = new ArrayList<>();
            List<Integer> sceneWeightList = new ArrayList<>();
            for (ElementalBondsSceneConfig sceneConfig : ElementalBondsConfigMgr.getInstance().getSceneConfigMap().values()) {
                if (eElementalBondsSceneType.contains(sceneConfig.getId()) && sceneConfig.getIsOpen() > 0) {
                    // Id
                    sceneIdList.add(sceneConfig.getId());
                    // Weight
                    sceneWeightList.add(ElementalBondsParam.getInstance().getSceneBaseWeight() + sceneWeightMap.getOrDefault(sceneConfig.getId(), 0));
                }
            }

            // 随机
            Random random = new Random();
            List<Integer> randomSceneList = new ArrayList<>();
            for (int i = 0; i < ElementalBondsParam.getInstance().getSceneDayNum() && !sceneIdList.isEmpty(); i++) {
                int index = RandomHelper.getRandomIndexByWeight(sceneWeightList, random);
                int sceneId = sceneIdList.remove(index);
                sceneWeightList.remove(index);
                randomSceneList.add(sceneId);
            }
            if (randomSceneList.isEmpty()) {
                randomSceneList.add(ElementalBondsSceneType.EMPTY.getType()); // 容错：至少一个盘面机制
            }

            // 设置
            elementBondsSystemInfo.setSceneList(randomSceneList);
            elementBondsSystemInfo.setSceneRefreshTime(now);

            // 计算下次权重加成
            for (ElementalBondsSceneConfig sceneConfig : ElementalBondsConfigMgr.getInstance().getSceneConfigMap().values()) {
                int sceneId = sceneConfig.getId();
                if (eElementalBondsSceneType.contains(sceneId)) {
                    if (randomSceneList.contains(sceneId)) {
                        sceneWeightMap.put(sceneId, 0);
                    } else {
                        sceneWeightMap.put(sceneId, sceneWeightMap.getOrDefault(sceneId, 0) + ElementalBondsParam.getInstance().getSceneWeightAdd());
                    }
                }
            }
            elementBondsSystemInfo.setChangeState();
        }

        // 同步到活动内渠道
        for (CrossElementalBondsGroupMgr groupMgr : groupMgrMap.values()) {
            ElementalBondsGroupData elementalBondsGroupData = groupMgr.getElementalBondsGroupData();
            for (Long serverId : elementalBondsGroupData.getServerIdList()) {
                ChannelElementalBondsSyncDaySceneRespMsg.Builder builder = ChannelElementalBondsSyncDaySceneRespMsg.newBuilder();
                builder.addAllSceneId(elementBondsSystemInfo.getSceneList());

                YanQuMessage message = YanQuMessageUtils.buildMessage(ElementalBondsPb.ELEMENTAL_BONDS_SYNC_DAY_SCENE_VALUE, builder);
                CrossAgentLinkedControl.send(serverId, message);
            }

        }
    }


    private void checkSceneDelete() {
        List<Integer> sceneList = new ArrayList<>(elementBondsSystemInfo.getSceneList());
        for (Integer sceneId : sceneList) {
            ElementalBondsSceneConfig sceneConfig = ElementalBondsConfigMgr.getInstance().getSceneConfigMap().get(sceneId);
            if (sceneConfig != null && eElementalBondsSceneType.contains(sceneId)) {
                continue;
            } else {
                checkRefreshScene(true);
                logger.error("-------------------- 今日盘面机制有问题，已重新刷新，问题ID {} ----------", sceneId);
                break;
            }
        }
    }

    // --------------------------- 匹配 --------------------------------
    private long randomMatchUserId(List<Long> playerIdList, List<Long> hadMatchList) {
        RandomHelper randomHelper = new RandomHelper();
        List<Long> list = new ArrayList<>();
        for (Long userId : playerIdList) {
            if (hadMatchList.contains(userId)) {
                continue;
            }
            list.add(userId);
        }
        if (list.isEmpty()) {
            return GameErrorCode.E_SUCCESS_VALUE;
        }
        return list.get(randomHelper.next(0, list.size()));
    }

    public void matchByTimer() {
        try {
            long nowTime = TimeUtils.currentTimeMillis();

            // 匹配模式,段位ID,匹配数据
            Map<Integer, Map<Integer, List<ElementalBondsMatchData>>> rankUserList = new HashMap<>();
            Map<Integer, ElementalBondsScoreConfig> rankConfigMap = ElementalBondsConfigMgr.getInstance().getScoreConfigMap();
            for (ElementalBondsModeType elementalBondsModeType : ElementalBondsModeType.values()) {
                if (elementalBondsModeType == ElementalBondsModeType.NONE) {
                    continue;
                }
                Map<Integer, List<ElementalBondsMatchData>> initRankList = new HashMap<>();
                for (ElementalBondsScoreConfig rankConfig : rankConfigMap.values()) {
                    initRankList.put(rankConfig.getId(), new ArrayList<>());
                }
                rankUserList.put(elementalBondsModeType.getType(), initRankList);
            }

            List<ElementalBondsMatchData> matchDataList;
            synchronized (lock) {
                // 获取匹配用户数据的副本以便在同步块外使用
                matchDataList = new ArrayList<>(matchingUserMap.values());
            }

            for (ElementalBondsMatchData matchData : matchDataList) {
                synchronized (lock) {
                    if (matchData.isCanceled()) {
                        continue;
                    }
                }

                int modeId = matchData.getModeId();
                if (matchData.isFirstPlay()) {
                    // 第一把
                    handleMatchNpc_First(matchData);
                } else if (matchData.getScore() <= ElementalBondsParam.getInstance().getMatchNpcScore() ||
                        matchData.isNovice() && TimeUtils.currentTimeMillis() - matchData.getBeginMatchTime() > ElementalBondsParam.getInstance().getGuideMatchNpcTime() * TimeUtils.SECONDS_MILLIS
                        || TimeUtils.currentTimeMillis() - matchData.getBeginMatchTime() > ElementalBondsParam.getInstance().getMatchOutTime() * TimeUtils.SECONDS_MILLIS) {
                    // 低分数先匹配人机 || 新手阶段 && 匹配时间过长 || 可以超时匹配 && 匹配时间超过最大值
                    handleMatchNpc(matchData);
                } else {
                    int rankId = ElementalBondsConfigMgr.getInstance().getScoreConfig(matchData.getScore(), matchData.isNovice()).getTitle();
                    rankUserList.get(modeId).get(rankId).add(matchData);
                }
            }

            // 匹配玩家
            for (Map.Entry<Integer, Map<Integer, List<ElementalBondsMatchData>>> entry : rankUserList.entrySet()) {
                matchPlayerByRank(entry.getKey(), entry.getValue());
            }

        } catch (Throwable e) {
            LogUtils.ERROR.error("CrossElementalBondsMgr.matchByTimer", e);
        }
    }


    private void matchPlayerByRank(int modeId, Map<Integer, List<ElementalBondsMatchData>> rankUserListMap) {
        Map<Integer, ElementalBondsScoreConfig> rankConfigMap = ElementalBondsConfigMgr.getInstance().getScoreConfigMap();

        // 【判断哪些段位可以主动匹配】
        List<Integer> canMatchList = new ArrayList<>();
        List<Integer> maxExtendList = new ArrayList<>();
        for (Map.Entry<Integer, List<ElementalBondsMatchData>> entry : rankUserListMap.entrySet()) {
            int rankId = entry.getKey();
            List<ElementalBondsMatchData> curRankUserList = entry.getValue();
            if (curRankUserList.isEmpty()) {
                continue;
            }

            // 按匹配时间慢慢扩大匹配范围
            long minMatchBeginTime = Long.MAX_VALUE;
            for (ElementalBondsMatchData matchData : curRankUserList) {
                synchronized (lock) {
                    if (matchData.isCanceled()) {
                        continue;
                    }
                }
                minMatchBeginTime = Math.min(matchData.getBeginMatchTime(), minMatchBeginTime);
            }
            int maxExtendTimes = ElementalBondsParam.getInstance().getMatchExtendTimes(TimeUtils.currentTimeMillis() - minMatchBeginTime);
            int minRank = rankId - maxExtendTimes;
            int maxRank = rankId + maxExtendTimes;

            // 扩大范围后不能超出配置范围
            List<Integer> matchRangeList = rankConfigMap.get(rankId).getMatchRangeList();
            minRank = Math.max(minRank, matchRangeList.get(0));
            maxRank = Math.min(maxRank, matchRangeList.get(1));

            // 统计范围内人数
            int count = 0;
            for (int i = minRank; i <= maxRank; i++) {
                count += rankUserListMap.get(i).size();
            }

            // 判断人数够不够
            if (count >= ElementalBondsParam.getInstance().getMatchNeedPlayerNum(rankId)) {
                canMatchList.add(rankId);
                maxExtendList.add(maxExtendTimes);
            }

        }
        // 【按段位小到大匹配】
        for (int i = 0; i < canMatchList.size(); i++) {
            int rankId = canMatchList.get(i);
            int maxExtendTimes = maxExtendList.get(i);

            // 拿需要匹配的人
            List<ElementalBondsMatchData> list = new ArrayList<>(rankUserListMap.get(rankId));

            // 拿被匹配的人（包含自己）
            List<ElementalBondsMatchData> otherList = new ArrayList<>(rankUserListMap.get(rankId));
            // 人数不够需要扩大范围
            List<Integer> matchRangeList = rankConfigMap.get(rankId).getMatchRangeList();
            for (int extendTimes = 0; extendTimes < maxExtendTimes; extendTimes++) {
                if (otherList.size() >= ElementalBondsParam.getInstance().getMatchNeedPlayerNum(rankId)) {
                    break;
                }

                boolean outOfRange = true;
                // 向左扩大一个段位
                int tmpRank1 = rankId - extendTimes - 1;
                if (matchRangeList.get(0) <= tmpRank1 && tmpRank1 <= matchRangeList.get(1)) {
                    otherList.addAll(rankUserListMap.get(tmpRank1));
                    outOfRange = false;
                }
                // 向右扩大一个段位
                int tmpRank2 = rankId + extendTimes + 1;
                if (matchRangeList.get(0) <= tmpRank2 && tmpRank2 <= matchRangeList.get(1)) {
                    otherList.addAll(rankUserListMap.get(tmpRank2));
                    outOfRange = false;
                }
                // 是否超范围
                if (outOfRange) {
                    break;
                }
            }

            // 打乱人员
            Collections.shuffle(list);
            Collections.shuffle(otherList);

            // 开始匹配
            for (ElementalBondsMatchData player1 : list) {
                if (otherList.size() < 2) {
                    break;
                }

                synchronized (lock) {
                    if (player1.isCanceled()) {
                        continue;
                    }
                }

                // 取出被匹配的玩家
                if (!otherList.remove(player1)) {
                    continue; // 已经被匹配走了
                }

                ElementalBondsMatchData player2 = otherList.get(0);
                synchronized (lock) {
                    if (player2.isCanceled()) {
                        continue;
                    }
                }
                //筛选卡牌
                int cardId = player1.getCardId();
                int cardId2 = player2.getCardId();
                ElementalBondsCardConfig cardConfig1 = ElementalBondsConfigMgr.getInstance().getCardConfigMap().get(cardId);
                ElementalBondsCardConfig cardConfig2 = ElementalBondsConfigMgr.getInstance().getCardConfigMap().get(cardId2);
                List<Integer> extraMatchRule = ElementalBondsParam.getInstance().getExtraMatchRule();
                if (cardConfig2.getQuality() < cardConfig1.getQuality() - extraMatchRule.get(0) || cardConfig2.getQuality() > cardConfig1.getQuality() + extraMatchRule.get(1)) {
                    continue;
                }
                List<Integer> extraMatchRate = ElementalBondsParam.getInstance().getExtraMatchRate();
                if (player2.getRate() < player1.getRate() - extraMatchRate.get(0) || player2.getRate() > player1.getRate() + extraMatchRate.get(1)) {
                    continue;
                }

                // 源list移除掉避免后面再被匹配
                synchronized (lock) {
                    rankUserListMap.get(ElementalBondsConfigMgr.getInstance().getScoreConfig(player1.getScore(), player1.isNovice()).getTitle()).remove(player1);
                    rankUserListMap.get(ElementalBondsConfigMgr.getInstance().getScoreConfig(player2.getScore(), player2.isNovice()).getTitle()).remove(player2);
                }
                // 匹配完成
                handleMatchPlayer(modeId, player1, player2);
            }
        }
    }
    private int randomSceneId(long score1, long score2) {
        int openLimit = ElementalBondsParam.getInstance().getFightSceneOpenLimit();
        boolean canScene = score1 >= openLimit && score2 >= openLimit;
        if (!canScene) {
            return ElementalBondsSceneType.EMPTY.getType();
        }

        List<Integer> sceneList = elementBondsSystemInfo.getSceneList();
        if (sceneList.isEmpty()) {
            return ElementalBondsSceneType.EMPTY.getType();
        }

        // 开始随机
        int index;
        synchronized (needLockRandom) {
            index = needLockRandom.nextInt(sceneList.size());
        }
        return sceneList.get(index);
    }

    private void handleMatchPlayer(int modeId, ElementalBondsMatchData player1, ElementalBondsMatchData player2) {
        synchronized (lock) {
            if (player1.isCanceled() || player2.isCanceled()) {
                return; // 再次检查匹配过程中是否被取消
            }
            matchingUserMap.remove(player1.getKey());
            matchingUserMap.remove(player2.getKey());
        }


        int randomSceneId = randomSceneId(player1.getScore(), player2.getScore());

        ElementalBondsModeType modeType = ElementalBondsModeType.forType(modeId);

        if (player1.isNovice() || player2.isNovice()) {
            modeType = ElementalBondsModeType.NEW_HAND_PVP;
        }
        if (modeType == ElementalBondsModeType.NONE) {
            modeType = ElementalBondsModeType.PVP;
        }

        ElementalBondsRoomData roomData = createRoom(modeType, randomSceneId);
        synchronized (roomData) {
            joinRoom(roomData, player1);
            joinRoom(roomData, player2);
            putRoomToMap(roomData);
            startGame(roomData);
        }

        // 预计匹配时间修改
        if (!player1.isAi() && !player2.isAi()) {
            long costTime1 = TimeUtils.currentTimeMillis() - player1.getBeginMatchTime();
            addMatchCostTime(costTime1);
            long costTime2 = TimeUtils.currentTimeMillis() - player2.getBeginMatchTime();
            addMatchCostTime(costTime2);
        }
    }

    private ElementalBondsMatchData createNpcMatchData(int cardId, List<Integer> skillList, long score, boolean newHand) {
        ElementalBondsScoreConfig rankConfig = ElementalBondsConfigMgr.getInstance().getScoreConfig(score, newHand);
        if (rankConfig == null) {
            LogUtils.ERROR.error("cardId {}, score {}, newHand {}, create Npc null", cardId, score, newHand);
            return null;
        }
        // 生成饰品
        List<Integer> decorationList = new ArrayList<>();
        int headId;
        int bodyId;
        List<Integer> npcHeadList = ElementalBondsParam.getInstance().getAiHeadList();
        List<Integer> npcBodyList = ElementalBondsParam.getInstance().getAiAppearance();
        synchronized (needLockRandom) {
            headId = npcHeadList.get(needLockRandom.nextInt(npcHeadList.size()));
            bodyId = npcBodyList.get(needLockRandom.nextInt(npcBodyList.size()));
        }
        // 生成名称
        int nameId;
        synchronized (needLockRandom) {
            nameId = needLockRandom.nextInt(ElementalBondsParam.getInstance().getNpcNameNum()) + 1;
        }
        // AI名字前缀
        String npcNamePrefix = "ElementalBonds_AI_NAME_";
        String nickname = npcNamePrefix + nameId;
        // 组装baseInfo
        // 生成卡片
        Map<Integer, ElementalBondsCardConfig> cardConfigMap = ElementalBondsConfigMgr.getInstance().getCardConfigMap();
        Set<Integer> aiCardSet = ElementalBondsConfigMgr.getInstance().getAiCardSet();
        int cardQua = cardConfigMap.get(cardId).getQuality();
        Map<Integer, List<Integer>> quaIdLMap = new HashMap<>(); // 符合条件的卡片都拿出来
        int maxAiCardQua = 0;
        if (cardQua == 5) {
            maxAiCardQua = 4;
        } else {
            maxAiCardQua = score >= ElementalBondsParam.getInstance().getAiRedCardLimit() ? 5 : 4;
        }
        for (ElementalBondsCardConfig cardConfig : cardConfigMap.values()) {
            if (aiCardSet.contains(cardConfig.getCardId())) {
                int tmpQua = cardConfig.getQuality();
                if (cardQua - 1 <= tmpQua && tmpQua <= cardQua + 1 && tmpQua <= maxAiCardQua) {
                    quaIdLMap.putIfAbsent(tmpQua, new ArrayList<>());
                    quaIdLMap.get(tmpQua).add(cardConfig.getCardId());
                }
            }
        }
        List<Integer> tmpWeightList = ElementalBondsParam.getInstance().getAiCardQuaWeightList(rankConfig.getTitle());
        List<Integer> weightList = new ArrayList<>(); // 各品质对应权重
        List<Integer> quaList = new ArrayList<>();
        for (int i = 0; i <= 2; i++) {
            int tmpQua = cardQua - 1 + i;
            if (quaIdLMap.containsKey(tmpQua)) {
                weightList.add(tmpWeightList.get(i));
                quaList.add(tmpQua);
            }
        }
        int quaIndex;
        synchronized (needLockRandom) {
            quaIndex = RandomHelper.getRandomIndexByWeight(weightList, needLockRandom);
        }
        int chooseQua = quaList.get(quaIndex);
        List<Integer> cardList = quaIdLMap.get(chooseQua); // 该品质所有卡片
        int chooseCardIndex;
        synchronized (needLockRandom) {
            chooseCardIndex = RandomHelper.getRandomIndexByWeight(cardList, needLockRandom);
        }
        int chooseCardId = cardList.get(chooseCardIndex);

        // AI只支持2种技能
        List<Integer> skills = new ArrayList<>();
        skills.add(ElementalBondsSkillType.SKILL_1001.getType());
        skills.add(ElementalBondsSkillType.SKILL_1002.getType());

        // 其余数据
        ElementalBondsMatchData matchData = new ElementalBondsMatchData();
        matchData.setBodyId(bodyId);
        matchData.setHeadId(headId);
        matchData.setName(nickname);
        matchData.setCardId(chooseCardId);
        matchData.setSkills(new ArrayList<>(skills));
        matchData.setAi(true);
        matchData.setScore(score);
        matchData.setNovice(newHand);

        matchData.setEffectId(0);
        matchData.setBoardSkinId(0);
        matchData.setAnimationId(0);
        return matchData;
    }

    // 新手第一把特殊处理
    private void handleMatchNpc_First(ElementalBondsMatchData matchData) {
        ElementalBondsMatchData npc = createNpcMatchData(matchData.getCardId(), matchData.getSkills(), matchData.getScore(), matchData.isNovice());

        matchingUserMap.remove(matchData.getKey());
        ElementalBondsRoomData roomData = createRoom(ElementalBondsModeType.GUIDE, ElementalBondsSceneType.EMPTY.getType());
        synchronized (roomData) {
            joinRoom(roomData, matchData);
            joinRoom(roomData, npc);
            putRoomToMap(roomData);

            // 指定先手玩家
            int turnPlayer = 1;
            // 第一回合多给他半小时时间
            ElementalBondsModeConfig modeConfig = ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(roomData.getModeId());
            long clearTime = modeConfig.getClearTime();
            long expireTime = TimeUtils.currentTimeMillis() + clearTime + DateHelper.MINUTE_MILLIONS * 30;
            // 拿初始盘面
            List<List<ElementalBondsBlock>> initBlockList = ElementalBondsGuideLogic.getBlockList(true, isPlayer1(roomData, matchData.getKey()));
            roomData.setRound(1);
            roomData.setExpireTime(expireTime);
            roomData.setTurnPlayer(turnPlayer);
            roomData.getPlayer1().setStep(modeConfig.getMaxStep() + 1);
            roomData.getPlayer2().setStep(modeConfig.getMaxStep());
            roomData.setGameData(ElementalBondsGameData.createElementalBondsGameData(roomData.getAppointSceneId(), initBlockList));
            roomData.setBeginTime(TimeUtils.currentTimeMillis());
            roomData.setBegin(true);

            ElementalBondsCardData card = roomData.getPlayer1().getElementalBondsCardData();
            card.setCardMp(card.getCardMaxMp() - 3);

            // 先同步游戏开始客户端才可以进去
            syncGameStart(roomData, roomData.getPlayer1().getElementalBondsMatchData(), roomData.getPlayer2().getElementalBondsMatchData());
        }
    }

    // 新手第一把特殊处理
    private void handleGuide_First(ElementalBondsMatchData matchData, ElementalBondsRoomData roomData, ElementalBondsModeConfig modeConfig) {
        if (roomData.isGuide() && !matchData.isFriendGuide()) {
            return;
        }
        int guideDropNo = 1;
        int turnPlayer = roomData.getTurnPlayer();
        ElementalBondsNoticeHelper noticeHelper = new ElementalBondsNoticeHelper(roomData.getModeId()); // 避免其他方法同步下去
        boolean isPlayer1 = isPlayer1(roomData, matchData.getKey());
        // 对话0
        ElementalBondsOperateResult result1 = new ElementalBondsOperateResult(ElementalBondsOperateType.GUIDE_STEP, true, turnPlayer);
        result1.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_STEP, 0));
        ElementalBondsOperateResultMsg.Builder msg1 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result1, roomData, matchData.getKey());

        // 移动1
        ElementalBondsOperateResult result2 = ElementalBondsGameLogic.operateMove(roomData, isPlayer1, ElementalBondsGuideLogic.getElementalBondsMoveParam(1), guideDropNo++, roomData.getRound());
        handleMoveResult(isPlayer1, roomData, result2, noticeHelper);
        ElementalBondsGuideLogic.replaceFirstEffect(result2.getElementalBondsOperateShowList(), ElementalBondsEffectType.EXCHANGE_BLOCK, ElementalBondsEffectType.GUIDE_OPERATE_MOVE);
        ElementalBondsOperateResultMsg.Builder msg2 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result2, roomData, matchData.getKey());

        // 对话1、2
        ElementalBondsOperateResult result21 = new ElementalBondsOperateResult(ElementalBondsOperateType.GUIDE_STEP, true, turnPlayer);
        result21.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_STEP, 1));
        result21.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_STEP, 2));
        ElementalBondsOperateResultMsg.Builder msg21 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result21, roomData, matchData.getKey());

        // 移动2
        ElementalBondsOperateResult result3 = ElementalBondsGameLogic.operateMove(roomData, isPlayer1, ElementalBondsGuideLogic.getElementalBondsMoveParam(2), guideDropNo++, roomData.getRound());
        handleMoveResult(isPlayer1, roomData, result3, noticeHelper);
        ElementalBondsGuideLogic.replaceFirstEffect(result3.getElementalBondsOperateShowList(), ElementalBondsEffectType.EXCHANGE_BLOCK, ElementalBondsEffectType.GUIDE_OPERATE_MOVE);
        ElementalBondsOperateResultMsg.Builder msg3 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result3, roomData, matchData.getKey());

        // 对话3
        ElementalBondsOperateResult result31 = new ElementalBondsOperateResult(ElementalBondsOperateType.GUIDE_STEP, true, turnPlayer);
        result31.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_STEP, 3));
        ElementalBondsOperateResultMsg.Builder msg31 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result31, roomData, matchData.getKey());

        // 移动3
        ElementalBondsOperateResult result4 = ElementalBondsGameLogic.operateMove(roomData, isPlayer1, ElementalBondsGuideLogic.getElementalBondsMoveParam(3), guideDropNo++, roomData.getRound());
        handleMoveResult(isPlayer1, roomData, result4, noticeHelper);
        ElementalBondsGuideLogic.replaceFirstEffect(result4.getElementalBondsOperateShowList(), ElementalBondsEffectType.EXCHANGE_BLOCK, ElementalBondsEffectType.GUIDE_OPERATE_MOVE);
        ElementalBondsOperateResultMsg.Builder msg4 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result4, roomData, matchData.getKey());

        // 对话4 技能对话0
        ElementalBondsOperateResult result41 = new ElementalBondsOperateResult(ElementalBondsOperateType.GUIDE_STEP, true, turnPlayer);
        result41.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_STEP, 4));
        result41.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_OPERATE_CARD, 0));
        ElementalBondsOperateResultMsg.Builder msg41 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result41, roomData, matchData.getKey());

        // 技能1
        ElementalBondsOperateResult result5 = ElementalBondsGameLogic.operateUseCard(roomData, isPlayer1, ElementalBondsGuideLogic.getElementalBondsCardParam1(matchData.getCardId()), guideDropNo++, roomData.getRound());
        handleUseCardResult(isPlayer1, roomData, result5, noticeHelper);
        ElementalBondsOperateResultMsg.Builder msg5 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result5, roomData, matchData.getKey());

        // 对话5、6、7
        ElementalBondsOperateResult result51 = new ElementalBondsOperateResult(ElementalBondsOperateType.GUIDE_STEP, true, turnPlayer);
        result51.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_STEP, 5));
        result51.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_STEP, 6));
        result51.addElementalBondsOperateShow(ElementalBondsGuideLogic.getClientShow(ElementalBondsEffectType.GUIDE_STEP, 7));
        ElementalBondsOperateResultMsg.Builder msg51 = CrossElementalBondsPb.parseElementalBondsOperateResultMsg(result51, roomData, matchData.getKey());

        // 重置下数据
        ElementalBondsPlayerData turnPlayerData = turnPlayer == 1 ? roomData.getPlayer1() : roomData.getPlayer2();
        turnPlayerData.setStep(modeConfig.getMaxStep());
        turnPlayerData.setScore(0);
        roomData.getGameData().setBlockList(ElementalBondsGuideLogic.getBlockList(false, isPlayer1));
        ElementalBondsOperateResultMsg.Builder msg6 = ElementalBondsOperateResultMsg.newBuilder().setSkillId(-1).setSide(roomData.getTurnPlayer()).setGameData(CrossElementalBondsPb.parseElementalBondsRoomDataMsg(roomData, roomData.getTurnPlayer()));
        // 返回
        noticeHelper = new ElementalBondsNoticeHelper(roomData.getModeId());
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg1), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg2), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg21), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg3), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg31), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg4), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg41), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg5), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg51), matchData, roomData.getModeId(), noticeHelper);
        sendNotice(ElementalBondsSyncNoticeRespMsg.newBuilder().setType(ElementalBondsOperateType.GUIDE_STEP.getType()).setOperate(msg6), matchData, roomData.getModeId(), noticeHelper);
        sendNoticeList(noticeHelper);
    }

    private void handleMatchNpc(ElementalBondsMatchData matchData) {
        ElementalBondsMatchData npc = createNpcMatchData(matchData.getCardId(), matchData.getSkills(), matchData.getScore(), matchData.isNovice());

        matchingUserMap.remove(matchData.getKey());
        int randomSceneId = randomSceneId(matchData.getScore(), matchData.getScore());
        ElementalBondsRoomData roomData = createRoom(matchData.isNovice() ? ElementalBondsModeType.NEW_HAND_PVP : ElementalBondsModeType.PVP, randomSceneId);
        synchronized (roomData) {
            joinRoom(roomData, matchData);
            joinRoom(roomData, npc);
            putRoomToMap(roomData);
            startGame(roomData);
        }
    }

    private void addMatchCostTime(long costTime) {
        synchronized (matchTimeList) {
            matchTimeList.add(costTime);
            if (matchTimeList.size() > MATCH_TIME_PARAM) {
                matchTimeList.removeFirst();
            }
        }
    }

    private int match(ElementalBondsMatchData matchData) {
        synchronized (lock) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            // 开始匹配
            matchData.setBeginMatchTime(TimeUtils.currentTimeMillis());
            matchingUserMap.put(matchData.getKey(), matchData);
            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private List<ElementalBondsMatchData> getShowOpponentList(String selfKey) {
        List<ElementalBondsMatchData> list = new ArrayList<>();
        for (ElementalBondsMatchData data : matchingUserMap.values()) {
            // 跳过自己
            if (Objects.equals(data.getKey(), selfKey)) {
                continue;
            }
            // 添加展示的玩家
            list.add(data);
            if (list.size() >= SHOW_OPPONENT_NUM) {
                break;
            }
        }

        // 人数不够就加点假数据
        if (list.size() < SHOW_OPPONENT_NUM) {
            Optional<ElementalBondsMatchData> first = matchingUserMap.values().stream().findFirst();
            if (first.isPresent()) {
                ElementalBondsMatchData matchData = first.get();
                while (list.size() < SHOW_OPPONENT_NUM) {
                    ElementalBondsMatchData npcMatchData = createNpcMatchData(matchData.getCardId(), matchData.getSkills(), matchData.getScore(), matchData.isNovice());
                    if (npcMatchData == null) {
                        continue;
                    }
                    list.add(npcMatchData);
                }
            }
        }

        return list;
    }

    private long getAvgMatchTime() {
        if (matchTimeList.size() < MATCH_TIME_PARAM) {
            return SHOW_MAX_MATCH_TIME;
        }
        LinkedList<Long> list = new LinkedList<>(matchTimeList);
        long avgTime = list.stream().mapToLong(Long::longValue).sum() / list.size();
        return Math.min(SHOW_MAX_MATCH_TIME, avgTime);
    }

    private boolean cancelMatch(String key) {
        synchronized (lock) {
            if (roomUserMap.containsKey(key)) {
                return false;
            }
            ElementalBondsMatchData matchData = matchingUserMap.get(key);
            if (matchData != null) {
                matchData.setCanceled(true); // 设置取消标记
                matchingUserMap.remove(key);
            }
            return true;
        }
    }

    private ElementalBondsMatchData parseElementalBondsUserMatchData(ChannelElementalBondsMatchReqMsg reqMsg) {
        ElementalBondsMatchData matchData = new ElementalBondsMatchData();
        ChannelElementalBondsUserKeyMsg userKey = reqMsg.getUserKey();
        matchData.setChannelId(userKey.getChannelId());
        matchData.setServerId(userKey.getServerId());
        matchData.setPlayerId(userKey.getPlayerId());
        // matchData.setBaseInfo(PlayerBasePb.parseToUserBaseInfo(reqMsg.getUserBaseInfo()));
        matchData.setScore(reqMsg.getScore());
        matchData.setCardId(reqMsg.getCardId());
        matchData.setSkills(reqMsg.getSkillIdList());
        matchData.setKey(getKey(userKey));
        //matchData.setServerName(ChannelXServerNameMgr.getServerNameMap().getServerName(userKey.getChannelId(), userKey.getServerId()));
        matchData.setNovice(reqMsg.getIsGuide());
        matchData.setFirstPlay(reqMsg.getFirstPlay());
        matchData.setModeId(reqMsg.getModeId());
        matchData.setFriendGuide(reqMsg.getFriendGuide());
        matchData.setBoardSkinId(reqMsg.getBoardSkinId());
        matchData.setAnimationId(reqMsg.getAnimationId());
        matchData.setEffectId(reqMsg.getEffectId());
        matchData.setRate(reqMsg.getWinRate());
        return matchData;
    }


    private String getKey(ChannelElementalBondsUserKeyMsg userKey) {
        // 修改格式需同时修改getNpcKey()
        return userKey.getChannelId() + "_" + userKey.getServerId() + "_" + userKey.getPlayerId();
    }

    // --------------------------- 房间 --------------------------------

    private ElementalBondsRoomData createRoom(ElementalBondsModeType modeType, int appointSceneId) {
        ElementalBondsRoomData roomData = new ElementalBondsRoomData();
        roomData.setRoomId(roomMaxId.getAndIncrement());
        roomData.setModeId(modeType.getType());
        roomData.setBegin(false);
        roomData.setAppointSceneId(appointSceneId);
        return roomData;
    }

    private void putRoomToMap(ElementalBondsRoomData roomData) {
        roomMap.put(roomData.getRoomId(), roomData);
    }

    private int joinRoom(ElementalBondsRoomData roomData, ElementalBondsMatchData matchData) {
        ElementalBondsPlayerData userGameData = ElementalBondsPlayerData.createData(matchData);
        joinRoomInitReady(roomData, userGameData);

        if (roomData.getPlayer1() == null) {
            roomData.setPlayer1(userGameData);
        } else if (roomData.getPlayer2() == null) {
            roomData.setPlayer2(userGameData);
        } else {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_FULL;
        }
        matchData.setRoomId(roomData.getRoomId());
        if (!matchData.isAi()) {
            roomUserMap.put(matchData.getKey(), matchData);
        }

        // 记录玩家自己的匹配时间
        matchData.setMatchCostTime(TimeUtils.currentTimeMillis() - matchData.getBeginMatchTime());

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private void joinRoomInitReady(ElementalBondsRoomData roomData, ElementalBondsPlayerData willJoinUser) {
        // 好友对战玩家2进房间默认是未准备状态
        if (roomData.getModeId() == ElementalBondsModeType.FRIEND_PK.getType() && roomData.getPlayer1() != null && roomData.getPlayer2() == null) {
            willJoinUser.setReady(false);
        }
    }

    private boolean isRoomPlaying(ElementalBondsRoomData elementalBondsRoomData) {
        return elementalBondsRoomData.isBegin() && elementalBondsRoomData.getWinPlayer() == 0;
    }

    private void removeRoom(ElementalBondsRoomData elementalBondsRoomData) {
        removeRoomPlayerData(elementalBondsRoomData.getPlayer1());
        removeRoomPlayerData(elementalBondsRoomData.getPlayer2());
        roomMap.remove(elementalBondsRoomData.getRoomId());
    }

    private void removeRoomPlayerData(ElementalBondsPlayerData elementalBondsPlayerData) {
        if (elementalBondsPlayerData != null) {
            ElementalBondsMatchData elementalBondsMatchData = elementalBondsPlayerData.getElementalBondsMatchData();
            if (!elementalBondsMatchData.isAi()) {
                roomUserMap.remove(elementalBondsMatchData.getKey());
            }
        }
    }

    private int player2QuitRoom(ElementalBondsRoomData roomData) {
        ElementalBondsPlayerData player2 = roomData.getPlayer2();
        if (player2 == null) {
            return GameErrorCode.E_SUCCESS_VALUE;
        }

        roomData.setPlayer2(null);
        roomData.getSkillPool().remove(2);
        roomData.getCardPool().remove(2);
        roomUserMap.remove(player2.getElementalBondsMatchData().getKey());

        // 同步好友
        ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
        syncBuilder.setType(ElementalBondsFriendNoticeType.FRIEND_QUIT_ROOM.getType());
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, roomData.getPlayer1().getElementalBondsMatchData());

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private void masterRemoveRoom(ElementalBondsRoomData roomData) {
        // 解散
        removeRoom(roomData);

        // 同步房间解散给好友
        ElementalBondsPlayerData player2 = roomData.getPlayer2();
        if (player2 != null) {
            ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(ElementalBondsFriendNoticeType.MASTER_REMOVE_ROOM.getType());
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE, syncBuilder, player2.getElementalBondsMatchData());
        }
    }

    // --------------------------- 好友对战 --------------------------------

    private boolean friendCheckAllReady(ElementalBondsRoomData roomData) {
        return roomData.getModeId() == ElementalBondsModeType.FRIEND_PK.getType() && roomData.getPlayer1() != null && roomData.getPlayer1().isReady() && roomData.getPlayer2() != null && roomData.getPlayer2().isReady();
    }

    private boolean friendCheckAllSelectedCard(ElementalBondsRoomData roomData) {
        return roomData.getModeId() == ElementalBondsModeType.FRIEND_PK.getType() && roomData.getPlayer1() != null && roomData.getPlayer1().getElementalBondsMatchData().getCardId() > 0
                && roomData.getPlayer2() != null && roomData.getPlayer2().getElementalBondsMatchData().getCardId() > 0;
    }

    private boolean friendCheckAllSelectedSkill(ElementalBondsRoomData roomData) {
        return roomData.getModeId() == ElementalBondsModeType.FRIEND_PK.getType() && roomData.getPlayer1() != null && !roomData.getPlayer1().getElementalBondsMatchData().getSkills().isEmpty()
                && roomData.getPlayer2() != null && !roomData.getPlayer2().getElementalBondsMatchData().getSkills().isEmpty();
    }

    private int friendOpenRoom(ElementalBondsMatchData matchData, int sceneId, List<Integer> cardList, List<Integer> skillList) {

        // 盘面机制是否开放
        ElementalBondsSceneConfig sceneConfig = ElementalBondsConfigMgr.getInstance().getSceneConfigMap().get(sceneId);
        if (sceneConfig == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_SCENE_CONFIG_NO_EXIST;
        }
        if (sceneConfig.getIsOpen() <= 0 || !eElementalBondsSceneType.contains(sceneId)) {
            return GameErrorCode.E_ELEMENTAL_BONDS_SCENE_NO_EXIST;
        }

        synchronized (lock) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            // 创建房间
            ElementalBondsRoomData room = createRoom(ElementalBondsModeType.FRIEND_PK, sceneId);
            room.getCardPool().put(1, cardList);
            room.getSkillPool().put(1, skillList);
            synchronized (room) {
                joinRoom(room, matchData);
                putRoomToMap(room);
            }
            // 返回
            ElementalBondsFriendCreateRoomRespMsg.Builder builder = ElementalBondsFriendCreateRoomRespMsg.newBuilder();
            builder.setRet(0);
            builder.setRoomInfo(CrossElementalBondsPb.parseElementalBondsFriendRoomInfoMsg(room, isPlayer1(room, matchData.getKey())));
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_CREATE_ROOM_VALUE % PbDefine.ToGamePbDefine, builder, matchData);
            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private int friendJoinRoom(ElementalBondsMatchData matchData, long roomId, List<Integer> cardList, List<Integer> skillList) {
        synchronized (lock) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            // 获取房间
            ElementalBondsRoomData roomData = roomMap.get(roomId);
            if (roomData == null) {
                return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
            }
            if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
            }
            // 加入房间
            synchronized (roomData) {
                roomData.getSkillPool().put(2, skillList);
                roomData.getCardPool().put(2, cardList);
                ElementalBondsPlayerData otherPlayer = roomData.getOtherPlayer(matchData.getPlayerId());
                if (otherPlayer != null) {
                    matchData.setCardId(otherPlayer.getElementalBondsMatchData().getCardId());
                }
                int ret = joinRoom(roomData, matchData);
                if (ret != GameErrorCode.E_SUCCESS_VALUE) {
                    return ret;
                }

            }

            // 返回
            ElementalBondsFriendJoinRoomRespMsg.Builder builder = ElementalBondsFriendJoinRoomRespMsg.newBuilder();
            builder.setRet(0);
            builder.setRoomInfo(CrossElementalBondsPb.parseElementalBondsFriendRoomInfoMsg(roomData, isPlayer1(roomData, matchData.getKey())));
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_JOIN_ROOM_VALUE % PbDefine.ToGamePbDefine, builder, matchData);

            // 同步好友
            ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(ElementalBondsFriendNoticeType.FRIEND_JOIN_ROOM.getType());
            syncBuilder.setFriend(CrossElementalBondsPb.parseElementalBondsOpponentMsg(roomData.getPlayer2().getElementalBondsMatchData()));
            syncBuilder.setArg(roomData.getPlayer2().getElementalBondsMatchData().getCardId());
            CrossElementalBondsPb.buildPoolData(roomData, syncBuilder);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, roomData.getPlayer1().getElementalBondsMatchData());

            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private int friendQuitRoom(String key) {

        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return GameErrorCode.E_SUCCESS_VALUE;
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_SUCCESS_VALUE;
        }
        if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
        }

        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_START;
            }

            boolean isPlayer1 = isPlayer1(roomData, key);
            if (isPlayer1) {
                masterRemoveRoom(roomData);
            } else {
                // 离开
                int ret = player2QuitRoom(roomData);
                if (ret != 0) {
                    return ret;
                }
            }
        }
        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private int friendStepOut(String key) {
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
        }
        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_START;
            }
            boolean isPlayer1 = isPlayer1(roomData, key);
            ElementalBondsPlayerData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
            userGameData.setStepOutTime(TimeUtils.currentTimeMillis());

            // 同步好友
            ElementalBondsPlayerData opponent = isPlayer1 ? roomData.getPlayer2() : roomData.getPlayer1();
            if (opponent != null) {
                ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
                syncBuilder.setType(ElementalBondsFriendNoticeType.MASTER_STEP_OUT.getType());
                sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, opponent.getElementalBondsMatchData());
            }
            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private int friendReturnRoom(String key, List<Integer> cardList, List<Integer> skillList) {
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
        }
        if (roomData.isBegin()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_GAME_START;
        }
        boolean isPlayer1;
        synchronized (roomData) {
            isPlayer1 = isPlayer1(roomData, key);
            ElementalBondsPlayerData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
            userGameData.setStepOutTime(0);
            if (isPlayer1) {
                roomData.getSkillPool().put(1, skillList);
                roomData.getCardPool().put(1, cardList);
            } else {
                roomData.getSkillPool().put(2, skillList);
                roomData.getCardPool().put(2, cardList);
            }
        }
        // 返回
        ElementalBondsFriendEnterMyRoomRespMsg.Builder builder = ElementalBondsFriendEnterMyRoomRespMsg.newBuilder();
        builder.setRet(0);
        builder.setRoomInfo(CrossElementalBondsPb.parseElementalBondsFriendRoomInfoMsg(roomData, isPlayer1));
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_ENTER_MY_ROOM_VALUE % PbDefine.ToGamePbDefine, builder, matchData);
        // 同步好友
        ElementalBondsPlayerData opponent = isPlayer1 ? roomData.getPlayer2() : roomData.getPlayer1();
        if (opponent != null) {
            ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(ElementalBondsFriendNoticeType.MASTER_RETURN_ROOM.getType());
            CrossElementalBondsPb.buildPoolData(roomData, syncBuilder);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, opponent.getElementalBondsMatchData());
        }
        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private int friendStartGame(String key) {
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
        }
        synchronized (lock) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_START;
            }
            if (roomData.getPlayer1() == null || roomData.getPlayer2() == null) {
                return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_FULL;
            }
            if (!friendCheckAllReady(roomData) || !friendCheckAllSelectedCard(roomData) || !friendCheckAllSelectedSkill(roomData)) {
                return GameErrorCode.E_ELEMENTAL_BONDS_OPPONENT_NO_READY;
            }

            boolean isPlayer1 = isPlayer1(roomData, key);
            if (!isPlayer1) {
                return GameErrorCode.E_ELEMENTAL_BONDS_NOT_OWNER;
            }
            if (roomData.getPlayer2().getStepOutTime() > 0) {
                return GameErrorCode.E_ELEMENTAL_BONDS_OPPONENT_STEP_OUT;
            }
            if (roomData.getPlayer1().getStepOutTime() > 0) {
                roomData.getPlayer1().setStepOutTime(0); // 容错，房主都能开始游戏了，肯定在界面了
            }
            startGame(roomData);

            List<ElementalBondsMatchData> list = new ArrayList<>();
            list.add(roomData.getPlayer1().getElementalBondsMatchData());
            list.add(roomData.getPlayer2().getElementalBondsMatchData());

            // 同步好友
            for (ElementalBondsMatchData tmp : list) {
                ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
                syncBuilder.setType(ElementalBondsFriendNoticeType.MASTER_START_GAME.getType());
                sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, tmp);
            }

            // 让游戏服卡开始ping
            for (ElementalBondsMatchData tmp : list) {
                ChannelElementalBondsStatusRespMsg.Builder statusBuilder = ChannelElementalBondsStatusRespMsg.newBuilder();
                statusBuilder.setType(1);
                statusBuilder.setModeId(matchData.getModeId());
                sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_STATUS_FROM_CROSS_VALUE, statusBuilder, tmp);
            }

            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private int friendQuickChat(String key, int chatIndex) {
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
        }

        boolean player1 = isPlayer1(roomData, key);
        ElementalBondsPlayerData opponent = player1 ? roomData.getPlayer2() : roomData.getPlayer1();
        if (opponent != null) {
            ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(ElementalBondsFriendNoticeType.SEND_QUICK_CHAT.getType());
            syncBuilder.setArg(chatIndex);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, opponent.getElementalBondsMatchData());
        }

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private int friendChangeCard(String key, int cardId, List<Integer> skillIdList) {
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
        }

        boolean isPlayer1;
        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_START;
            }

            isPlayer1 = isPlayer1(roomData, key);
            ElementalBondsPlayerData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();

            if (!isPlayer1 && userGameData.isReady()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_CHANGE_CARD_NO_READY;
            }
            Set<Integer> cardPoolSet = roomData.getCardPoolSet();
            if (!cardPoolSet.contains(cardId)) return GameErrorCode.E_ELEMENTAL_BONDS_CARD_NO_UNLOCK;
            Set<Integer> skillPoolSet = roomData.getSkillPoolSet();
            for (Integer skillId : skillIdList) {
                if (!skillPoolSet.contains(skillId)) return GameErrorCode.E_ELEMENTAL_BONDS_SKILL_NO_UNLOCK;
            }
            // 换卡片
            ElementalBondsCardConfig cardConfig = ElementalBondsConfigMgr.getInstance().getCardConfigMap().get(cardId);
            userGameData.getElementalBondsMatchData().setCardId(cardConfig.getCardId());
            userGameData.setElementalBondsCardData(new ElementalBondsCardData(cardConfig.getCardId(), 0, cardConfig.getCd()));
            // 换技能
            if (!skillIdList.isEmpty()) {
                userGameData.getElementalBondsMatchData().setSkills(skillIdList);
                List<ElementalBondsSkillData> skillList = new ArrayList<>();
                for (int skillId : skillIdList) {
                    skillList.add(new ElementalBondsSkillData(skillId, ElementalBondsParam.getInstance().getSkillIdUseLimit()));
                }
                userGameData.setElementalBondsSkillDataList(skillList);
            }
        }

        // 同步
        List<ElementalBondsPlayerData> list = new ArrayList<>();
        ElementalBondsPlayerData player1 = roomData.getPlayer1();
        if (player1 != null) {
            list.add(player1);
        }
        ElementalBondsPlayerData player2 = roomData.getPlayer2();
        if (player2 != null) {
            list.add(player2);
        }
        for (ElementalBondsPlayerData tmp : list) {
            ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(isPlayer1 ? ElementalBondsFriendNoticeType.PLAYER1_CHANGE_CARD.getType() : ElementalBondsFriendNoticeType.PLAYER2_CHANGE_CARD.getType());
            syncBuilder.setArg(cardId);
            syncBuilder.addAllArg2(skillIdList);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, tmp.getElementalBondsMatchData());
        }

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private int friendReady(String key, boolean ready) {
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
        }

        boolean isPlayer1;
        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_START;
            }

            isPlayer1 = isPlayer1(roomData, key);
            ElementalBondsPlayerData userGameData = isPlayer1 ? roomData.getPlayer1() : roomData.getPlayer2();
            userGameData.setReady(ready);
        }

        // 同步
        List<ElementalBondsPlayerData> list = new ArrayList<>();
        ElementalBondsPlayerData player1 = roomData.getPlayer1();
        if (player1 != null) {
            list.add(player1);
        }
        ElementalBondsPlayerData player2 = roomData.getPlayer2();
        if (player2 != null) {
            list.add(player2);
        }
        for (ElementalBondsPlayerData tmp : list) {
            ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(ElementalBondsFriendNoticeType.PLAYER2_READY.getType());
            syncBuilder.setArg(ready ? 1 : 0);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, tmp.getElementalBondsMatchData());
        }

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private int friendChangeScene(String key, int sceneId) {
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return getNoStatusErrorCode();
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST;
        }
        if (roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            return GameErrorCode.E_ELEMENTAL_BONDS_ROOM_TYPE_ERROR;
        }

        // 盘面机制是否开放
        ElementalBondsSceneConfig sceneConfig = ElementalBondsConfigMgr.getInstance().getSceneConfigMap().get(sceneId);
        if (sceneConfig == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_SCENE_CONFIG_NO_EXIST;
        }
        if (sceneConfig.getIsOpen() <= 0 || !eElementalBondsSceneType.contains(sceneId)) {
            return GameErrorCode.E_ELEMENTAL_BONDS_SCENE_NO_EXIST;
        }

        synchronized (roomData) {
            if (roomData.isBegin()) {
                return GameErrorCode.E_ELEMENTAL_BONDS_GAME_START;
            }

            if (!isPlayer1(roomData, key)) {
                return GameErrorCode.E_ELEMENTAL_BONDS_NOT_OWNER;
            }

            roomData.setAppointSceneId(sceneId);
        }

        // 同步
        List<ElementalBondsPlayerData> list = new ArrayList<>();
        ElementalBondsPlayerData player1 = roomData.getPlayer1();
        if (player1 != null) {
            list.add(player1);
        }
        ElementalBondsPlayerData player2 = roomData.getPlayer2();
        if (player2 != null) {
            list.add(player2);
        }
        for (ElementalBondsPlayerData tmp : list) {
            ElementalBondsFriendSyncRoomRespMsg.Builder syncBuilder = ElementalBondsFriendSyncRoomRespMsg.newBuilder();
            syncBuilder.setType(ElementalBondsFriendNoticeType.CHANGE_SCENE.getType());
            syncBuilder.setArg(sceneId);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SYNC_ROOM_VALUE % PbDefine.ToGamePbDefine, syncBuilder, tmp.getElementalBondsMatchData());
        }

        return GameErrorCode.E_SUCCESS_VALUE;
    }

    private int practice(ElementalBondsMatchData matchData, int sceneId) {

        // 盘面机制是否开放
        ElementalBondsSceneConfig sceneConfig = ElementalBondsConfigMgr.getInstance().getSceneConfigMap().get(sceneId);
        if (sceneConfig == null) {
            return GameErrorCode.E_ELEMENTAL_BONDS_SCENE_CONFIG_NO_EXIST;
        }
        if (sceneConfig.getIsOpen() <= 0 || !eElementalBondsSceneType.contains(sceneId)) {
            return GameErrorCode.E_ELEMENTAL_BONDS_SCENE_NO_EXIST;
        }

        synchronized (lock) {
            // 正在匹配中 || 正在房间中
            if (matchingUserMap.containsKey(matchData.getKey()) || roomUserMap.containsKey(matchData.getKey())) {
                return getStatusErrorCode(matchData.getKey());
            }
            ElementalBondsMatchData npc = createNpcMatchData(matchData.getCardId(), matchData.getSkills(), matchData.getScore(), matchData.isNovice());
            ElementalBondsRoomData roomData = createRoom(ElementalBondsModeType.PRACTICE, sceneId);
            synchronized (roomData) {
                joinRoom(roomData, matchData);
                joinRoom(roomData, npc);
                putRoomToMap(roomData);
                startGame(roomData);
            }
            return GameErrorCode.E_SUCCESS_VALUE;
        }
    }

    private ElementalBondsMatchData createNewUserMatchData(ElementalBondsMatchData old) {
        ElementalBondsMatchData matchData = new ElementalBondsMatchData();
        matchData.setChannelId(old.getChannelId());
        matchData.setServerId(old.getServerId());
        matchData.setPlayerId(old.getPlayerId());
        matchData.setHeadId(old.getHeadId());
        matchData.setName(old.getName());
        matchData.setBodyId(old.getBodyId());
        matchData.setScore(old.getScore());
        matchData.setCardId(old.getCardId());
        matchData.setSkills(old.getSkills());
        matchData.setKey(old.getKey());
        //matchData.setServerName(ChannelXServerNameMgr.getServerNameMap().getServerName(old.getChannelId(), old.getServerId()));
        matchData.setNovice(old.isNovice());
        matchData.setFirstPlay(old.isFirstPlay());
        matchData.setModeId(old.getModeId());
        matchData.setBoardSkinId(old.getBoardSkinId());
        matchData.setAnimationId(old.getAnimationId());
        matchData.setEffectId(old.getEffectId());
        return matchData;
    }

    public void removeExpireErrorRoom() {
        for (ElementalBondsRoomData roomData : roomMap.values()) {
            synchronized (roomData) {

                long now = TimeUtils.currentTimeMillis();

                // 好友对战创建1小时未开战斗 || 练习模式对战超过12小时移除 || 非练习模式对战超过1小时移除
                if (roomData.getModeId() == ElementalBondsModeType.FRIEND_PK.getType() & !roomData.isBegin() && now - roomData.getCreateTime() > DateHelper.HOUR_MILLIONS
                        || roomData.getModeId() == ElementalBondsModeType.PRACTICE.getType() && roomData.isBegin() && now - roomData.getBeginTime() > DateHelper.HOUR_MILLIONS * 12
                        || roomData.getModeId() != ElementalBondsModeType.PRACTICE.getType() && roomData.isBegin() && now - roomData.getBeginTime() > DateHelper.HOUR_MILLIONS) {

                    // 移除房间
                    removeRoom(roomData);

                    // 打个日志
                    logger.error("remove expire error room userId1 {} userId2 {}.",
                            roomData.getPlayer1() != null ? roomData.getPlayer1().getElementalBondsMatchData().getPlayerId() : -1L,
                            roomData.getPlayer2() != null ? roomData.getPlayer2().getElementalBondsMatchData().getPlayerId() : -1L);
                }
            }
        }

        // 房间号900万后重置下避免位数太高，定时器调用
        if (roomMaxId.get() > 9000000) {
            roomMaxId.set(100);
        }
    }

    // --------------------------- CMD --------------------------------

    public void match(ChannelElementalBondsMatchReqMsg reqMsg) {
        ElementalBondsMatchData matchData = parseElementalBondsUserMatchData(reqMsg);

        int ret = 0;
//        if(matchData.getModeId() == ElementalBondsModeType.AreaPk.getValue()){
//            ChannelWashPkActivity activity = ChannelElementalBondsPkMgr.getInTimeActivityInfo(userKey.getChannelId(), userKey.getServerId());
//            if(activity != null){
//                UserEnterName enterName = ChannelEnterNameMgr.getUserEnterName(activity.getActivityInfo().getActivityId(), userKey.getUserId());
//                //未报名，或者未分组
//                if(enterName == null){
//                    ret = GameErrorCode.E_ENTER_NAME_NOT_IN_NAME;
//                } else if(enterName.getGroupId() == 0){
//                    ret = GameErrorCode.E_ENTER_NAME_NOT_IN_ACTIVITY_TIME;
//                } else {
//                    matchData.setZoneId(enterName.getZoneId());
//                    ChannelElementalBondsPkUserData userData = activity.getChannelUserData(userKey.getUserId(), null);
//                    if(userData != null){
//                        matchData.setWinTimesMax(userData.getMaxWinTimes());
//                        matchData.setWinTimes(userData.getWinTimes());
//                        matchData.setScore(userData.getScore());
//                    }
//                }
//            }else {
//                ret = GameErrorCode.E_ACTIVITY_OVER_TIME;
//            }
//        }

        // 匹配
        ret = match(matchData);
        ElementalBondsMatchRespMsg.Builder builder = ElementalBondsMatchRespMsg.newBuilder();
        builder.setRet(ret);
        if (ret == 0) {
            // 拿一下预计时间
            long matchTime = getAvgMatchTime();
            builder.setExpectMatchTime(matchTime);

            // 拿一下正在匹配的玩家
            List<ElementalBondsMatchData> showOpponentList = getShowOpponentList(matchData.getKey());
            for (ElementalBondsMatchData opponent : showOpponentList) {
                builder.addOpponent(CrossElementalBondsPb.parseElementalBondsOpponentMsg(opponent));
            }

            // 让游戏服卡开始ping
            ChannelElementalBondsStatusRespMsg.Builder statusBuilder = ChannelElementalBondsStatusRespMsg.newBuilder();
            statusBuilder.setType(1);
            statusBuilder.setModeId(matchData.getModeId());
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_STATUS_FROM_CROSS_VALUE, statusBuilder, matchData);
        }

        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_MATCH_VALUE % PbDefine.ToGamePbDefine, builder, reqMsg.getUserKey());
    }

    public void cancelMatch(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);
        boolean cancelMatch = cancelMatch(key);

        ElementalBondsCancelMatchRespMsg.Builder builder = ElementalBondsCancelMatchRespMsg.newBuilder();
        builder.setRet(0);
        builder.setMatchSuccess(!cancelMatch);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_CANCEL_MATCH_FROM_CROSS_VALUE, builder, userKey);
    }

    public void getRoomData(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);

        ElementalBondsRoomData roomData = null;
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            roomData = roomMap.get(matchData.getRoomId());
        }

        ElementalBondsGetRoomDataRespMsg.Builder builder = ElementalBondsGetRoomDataRespMsg.newBuilder();
        if (roomData != null) {
            builder.setRet(0);
            builder.setRoomData(CrossElementalBondsPb.parseElementalBondsRoomDataMsg(roomData, key));
        } else {
            builder.setRet(getNoStatusErrorCode());
        }
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_GET_ROOM_DATA_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void surrender(ChannelElementalBondsUserKeyMsg userKey, boolean timesLimit) {
        String key = getKey(userKey);

        int ret = surrender(key, timesLimit);
        ElementalBondsSurrenderRespMsg.Builder builder = ElementalBondsSurrenderRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_SURRENDER_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void sendExpression(ChannelElementalBondsUserKeyMsg userKey, int expressionId) {
        String key = getKey(userKey);

        int ret = sendExpression(key, expressionId);
        ElementalBondsOperateRespMsg.Builder builder = ElementalBondsOperateRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_OPERATE_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void guideSuccess(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);

        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
            if (roomData != null) {
                synchronized (roomData) {
                    ElementalBondsModeConfig modeConfig = ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(roomData.getModeId());

                    // 已开始，人机，第一回合，轮到玩家1，玩家1还是0分，进度条比配置的长1分钟
                    if (roomData.isBegin()
                            && roomData.getPlayer2().getElementalBondsMatchData().isAi()
                            && roomData.getRound() == 1
                            && roomData.getTurnPlayer() == 1
                            && roomData.getPlayer1().getScore() == 0
                            && roomData.getExpireTime() - TimeUtils.currentTimeMillis() > modeConfig.getClearTime() + DateHelper.MINUTE_MILLIONS) {
                        // 修改引导的超时时间
                        roomData.setExpireTime(TimeUtils.currentTimeMillis() + modeConfig.getClearTime());
                        // 同步引导超时时间
                        ElementalBondsSyncNoticeRespMsg.Builder builder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                        builder.setType(ElementalBondsOperateType.SYNC_EXPIRE_TIME.getType());
                        builder.setExpireTime(roomData.getExpireTime());
                        sendNotice(builder, matchData, roomData.getModeId(), null);
                    }
                    // 好友对战模式
                    if (roomData.isBegin()
                            && roomData.getModeId() == ElementalBondsModeType.FRIEND_PK.getType()
                            && roomData.isGuide()) {
                        // 结束引导
                        roomData.setGuide(false);
                        // 修改引导的超时时间
                        roomData.setExpireTime(TimeUtils.currentTimeMillis() + modeConfig.getClearTime());

                        // 同步
                        List<ElementalBondsMatchData> list = new ArrayList<>();
                        list.add(roomData.getPlayer1().getElementalBondsMatchData());
                        list.add(roomData.getPlayer2().getElementalBondsMatchData());
                        // 发条通知
                        for (ElementalBondsMatchData data : list) {
                            if (data != null && !data.isAi()) {
                                ElementalBondsSyncNoticeRespMsg.Builder builder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                                builder.setType(ElementalBondsOperateType.USE_SKILL.getType());
                                builder.setOperate(ElementalBondsOperateResultMsg.newBuilder()
                                        .setSide(roomData.getTurnPlayer())
                                        .setSkillId(-1)
                                        .setGameData(CrossElementalBondsPb.parseElementalBondsRoomDataMsg(roomData, data.getKey())));
                                sendNotice(builder, data, roomData.getModeId(), null);
                            }
                        }
                    }
                }
            }
        }

        ElementalBondsOperateRespMsg.Builder builder = ElementalBondsOperateRespMsg.newBuilder();
        builder.setRet(0);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_OPERATE_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void useCard(ChannelElementalBondsUserKeyMsg userKey, ElementalBondsCardParam param, long showNo) {
        String key = getKey(userKey);

        int ret = operateUseCard(key, param);
        ElementalBondsRoomDataMsg.Builder roomBuilder = checkRetRoomData(ret, key, showNo);

        ElementalBondsOperateRespMsg.Builder builder = ElementalBondsOperateRespMsg.newBuilder();
        builder.setRet(ret);
        if (roomBuilder != null) {
            builder.setRoomData(roomBuilder);
        }
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_OPERATE_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void useSkill(ChannelElementalBondsUserKeyMsg userKey, ElementalBondsSkillParam param, long showNo) {
        String key = getKey(userKey);

        int ret = operateUseSkill(key, param);
        ElementalBondsRoomDataMsg.Builder roomBuilder = checkRetRoomData(ret, key, showNo);

        ElementalBondsOperateRespMsg.Builder builder = ElementalBondsOperateRespMsg.newBuilder();
        builder.setRet(ret);
        if (roomBuilder != null) {
            builder.setRoomData(roomBuilder);
        }
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_OPERATE_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void move(ChannelElementalBondsUserKeyMsg userKey, ElementalBondsMoveParam param, long showNo) {
        String key = getKey(userKey);

        int ret = operateMove(key, param);
        ElementalBondsRoomDataMsg.Builder roomBuilder = checkRetRoomData(ret, key, showNo);

        ElementalBondsOperateRespMsg.Builder builder = ElementalBondsOperateRespMsg.newBuilder();
        builder.setRet(ret);
        if (roomBuilder != null) {
            builder.setRoomData(roomBuilder);
        }
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_OPERATE_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void showFinish(ChannelElementalBondsUserKeyMsg userKey, long showNo) {
        String key = getKey(userKey);

        showFinish(key, showNo);
    }

    public void disconnect(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);

        // 匹配的队列移除
        synchronized (lock) {
            matchingUserMap.remove(key);
        }

        // 有房间就设置掉线时间
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
            if (roomData != null) {
                synchronized (roomData) {
                    long disconnectTime = matchData.getDisconnectTimes() + ElementalBondsParam.getInstance().getDisconnFailTime(matchData.getDisconnectTimes());
                    matchData.setDisconnectTime(disconnectTime);

                    // 检查是否要发送给对方
                    checkSendDisconnect(null, roomData);

                    // 日志
                    addElementalBondsOperateLog(roomData, isPlayer1(roomData, key), ElementalBondsOperateLogType.DISCONNECT, 0);
                }
            }
        }
    }

    public void reConnect(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);

        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData == null) {
            return;
        }
        ElementalBondsRoomData roomData = roomMap.get(matchData.getRoomId());
        if (roomData == null) {
            return;
        }

        synchronized (roomData) {
            long disconnectTime = matchData.getDisconnectTime();
            if (disconnectTime > 0) {

                // 本次掉线时间
                long now = TimeUtils.currentTimeMillis();
                long costTime = now - disconnectTime;

                // 修改掉线状态
                matchData.setDisconnectTime(0);

                if (roomData.isBegin()) {
                    if (turnSelf(roomData, isPlayer1(roomData, key))) {

                        // 补掉线时间        增加的时间 = min ( 满时间 , 旧时间+掉线时间 ) - 当前时间
                        long clearTime = ElementalBondsConfigMgr.getInstance().getModeConfigMap().get(roomData.getModeId()).getClearTime();
                        long addTime = Math.min(now + clearTime, roomData.getExpireTime() + costTime) - roomData.getExpireTime();
                        roomData.addExpireTime(addTime);

                        // 游戏开始10秒后才记录掉线次数，避免进来慢了
                        if (now > roomData.getBeginTime() + ElementalBondsParam.getInstance().getDisconnectFailFirstLimit()) {
                            matchData.setDisconnectTimes(matchData.getDisconnectTimes() + 1);
                        }

                        // 同步重连状态 给对方
                        boolean isPlayer1 = Objects.equals(roomData.getPlayer1().getElementalBondsMatchData().getKey(), key);
                        ElementalBondsMatchData opponent = isPlayer1 ? roomData.getPlayer2().getElementalBondsMatchData() : roomData.getPlayer1().getElementalBondsMatchData();
                        if (!opponent.isAi()) {
                            ElementalBondsSyncNoticeRespMsg.Builder builder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                            builder.setType(ElementalBondsOperateType.OPPONENT_DISCONNECT.getType());
                            builder.setExpireTime(roomData.getExpireTime());
                            sendNotice(builder, opponent, roomData.getModeId(), null);
                        }
                    } else {
                        // 没有轮到自己，不算时间次数
                    }

                    // 同步重连状态 给自己
                    ElementalBondsSyncNoticeRespMsg.Builder builder = ElementalBondsSyncNoticeRespMsg.newBuilder();
                    builder.setType(ElementalBondsOperateType.RECONNECT.getType());
                    sendNotice(builder, matchData, roomData.getModeId(), null);
                }

                // 日志
                addElementalBondsOperateLog(roomData, isPlayer1(roomData, key), ElementalBondsOperateLogType.RECONNECT, 0);
            }
        }

    }

    public void getPlayingGame(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);

        ElementalBondsRoomData roomData = null;
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            roomData = roomMap.get(matchData.getRoomId());
        }

        ElementalBondsGetPlayingGameRespMsg.Builder builder = ElementalBondsGetPlayingGameRespMsg.newBuilder();
        if (roomData != null && isRoomPlaying(roomData)) {
            builder.setRoomData(CrossElementalBondsPb.parseElementalBondsRoomDataMsg(roomData, key));
        }
        builder.setRet(0);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_GET_PLAYING_GAME_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void friendJoinRoom(ChannelElementalBondsMatchReqMsg reqMsg, long roomId, List<Integer> cardList, List<Integer> skillList) {
        ElementalBondsMatchData matchData = parseElementalBondsUserMatchData(reqMsg);
        int ret = friendJoinRoom(matchData, roomId, cardList, skillList);
        if (ret != 0) {
            ElementalBondsFriendJoinRoomRespMsg.Builder builder = ElementalBondsFriendJoinRoomRespMsg.newBuilder();
            builder.setRet(ret);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_JOIN_ROOM_VALUE % PbDefine.ToGamePbDefine, builder, matchData);
        }
    }

    public void friendLeaveRoom(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);
        int ret = friendQuitRoom(key);

        ElementalBondsFriendLeaveRoomRespMsg.Builder builder = ElementalBondsFriendLeaveRoomRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_LEAVE_ROOM_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void friendOpenRoom(ChannelElementalBondsMatchReqMsg reqMsg, int sceneId, List<Integer> cardList, List<Integer> skillList) {
        ElementalBondsMatchData matchData = parseElementalBondsUserMatchData(reqMsg);
        int ret = friendOpenRoom(matchData, sceneId, cardList, skillList);
        if (ret != 0) {
            ElementalBondsFriendCreateRoomRespMsg.Builder builder = ElementalBondsFriendCreateRoomRespMsg.newBuilder();
            builder.setRet(ret);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_CREATE_ROOM_VALUE % PbDefine.ToGamePbDefine, builder, reqMsg.getUserKey());
        }
    }

    public void friendReturnRoom(ChannelElementalBondsUserKeyMsg userKey, List<Integer> cardList, List<Integer> skillList) {
        int ret = friendReturnRoom(getKey(userKey),cardList,skillList);
        if (ret != 0) {
            ElementalBondsFriendEnterMyRoomRespMsg.Builder builder = ElementalBondsFriendEnterMyRoomRespMsg.newBuilder();
            builder.setRet(0);
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_ENTER_MY_ROOM_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
        }
    }

    public void friendSearchRoom(ChannelElementalBondsUserKeyMsg userKey, long roomId) {
        ElementalBondsFriendSearchRoomRespMsg.Builder builder = ElementalBondsFriendSearchRoomRespMsg.newBuilder();

        ElementalBondsRoomData roomData = roomMap.get(roomId);
        if (roomData == null || roomData.getModeId() != ElementalBondsModeType.FRIEND_PK.getType()) {
            builder.setRet(GameErrorCode.E_ELEMENTAL_BONDS_ROOM_NO_EXIST);
        } else {
            builder.setRet(0);
            builder.addResult(CrossElementalBondsPb.parseElementalBondsFriendRoomInfoMsg(roomData, isPlayer1(roomData, getKey(userKey))));
            long playerId = roomData.getPlayer1().getElementalBondsMatchData().getPlayerId();
            PlayerShowData showData = PlayerRedisMgr.getInstance().getPlayerShowInfo(playerId);
            if (showData != null) {
                builder.setShowMsg(PlayerInfoPb.builderPlayerHeadAndNameMsg(showData));
            }
        }

        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_SEARCH_ROOM_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void friendStartGame(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);
        int ret = friendStartGame(key);

        ElementalBondsFriendStartGameRespMsg.Builder builder = ElementalBondsFriendStartGameRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_START_GAME_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void friendStepOut(ChannelElementalBondsUserKeyMsg userKey) {
        String key = getKey(userKey);
        int ret = friendStepOut(key);

        ElementalBondsFriendStepOutRespMsg.Builder builder = ElementalBondsFriendStepOutRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_STEP_OUT_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void practice(ChannelElementalBondsMatchReqMsg reqMsg) {
        ElementalBondsMatchData matchData = parseElementalBondsUserMatchData(reqMsg);
        int appointSceneId = reqMsg.getAppointSceneId();

        // 匹配
        int ret = practice(matchData, appointSceneId);

        ElementalBondsMatchRespMsg.Builder builder = ElementalBondsMatchRespMsg.newBuilder();
        builder.setRet(ret);
        if (ret == 0) {
            // 拿一下预计时间
            builder.setExpectMatchTime(0);

            // 拿一下正在匹配的玩家
            // 不需要了

            // 让游戏服卡开始ping
            ChannelElementalBondsStatusRespMsg.Builder statusBuilder = ChannelElementalBondsStatusRespMsg.newBuilder();
            statusBuilder.setType(1);
            statusBuilder.setModeId(matchData.getModeId());
            sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_STATUS_FROM_CROSS_VALUE, statusBuilder, matchData);
        }

        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_MATCH_VALUE % PbDefine.ToGamePbDefine, builder, reqMsg.getUserKey());
    }

    public void friendQuickChat(ChannelElementalBondsUserKeyMsg userKey, int chatId) {
        String key = getKey(userKey);
        int ret = friendQuickChat(key, chatId);

        ElementalBondsFriendQuickChatRespMsg.Builder builder = ElementalBondsFriendQuickChatRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_QUICK_CHAT_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void friendChangeCard(ChannelElementalBondsUserKeyMsg userKey, int cardId, List<Integer> skillIdList) {
        String key = getKey(userKey);
        int ret = friendChangeCard(key, cardId, skillIdList);

        ElementalBondsFriendChangeCardRespMsg.Builder builder = ElementalBondsFriendChangeCardRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_CHANGE_CARD_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void friendReady(ChannelElementalBondsUserKeyMsg userKey, boolean ready) {
        String key = getKey(userKey);
        int ret = friendReady(key, ready);

        ElementalBondsFriendReadyRespMsg.Builder builder = ElementalBondsFriendReadyRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_READY_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public void friendChangeScene(ChannelElementalBondsUserKeyMsg userKey, int sceneId) {
        String key = getKey(userKey);
        int ret = friendChangeScene(key, sceneId);

        ElementalBondsFriendChangeSceneRespMsg.Builder builder = ElementalBondsFriendChangeSceneRespMsg.newBuilder();
        builder.setRet(ret);
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_FRIEND_CHANGE_SCENE_VALUE % PbDefine.ToGamePbDefine, builder, userKey);
    }

    public int getDayScene(ChannelElementalBondsUserKeyMsg userKey) {
        // 检查0点刷新
        if (DateHelper.getTodayZeroTimeStamp() > elementBondsSystemInfo.getSceneRefreshTime()) {
            checkRefreshScene(false);
        }

        ChannelElementalBondsSyncDaySceneRespMsg.Builder builder = ChannelElementalBondsSyncDaySceneRespMsg.newBuilder();
        builder.addAllSceneId(elementBondsSystemInfo.getSceneList());
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_SYNC_DAY_SCENE_VALUE, builder, userKey);
        return GameErrorCode.E_SUCCESS_VALUE;
    }

    //更新排行榜
    public void updateRank(long playerId, long score, long serverId) {
        Long groupId = serverGroupMap.get(serverId);
        if (groupId == null) {
            return;
        }
        CrossElementalBondsGroupMgr groupMgr = groupMgrMap.get(groupId);
        if (groupMgr == null || !groupMgr.canAddRank()) {
            return;
        }
        groupMgr.addPlayerRank(playerId, score, serverId);
    }

    private void addElementalBondsOperateLog(ElementalBondsRoomData elementalBondsRoomData, boolean isPlayer1, ElementalBondsOperateLogType elementalBondsOperateLogType, int elementalBondsLogParam) {
        ElementalBondsPlayerData selfElementalBondsPlayerData = isPlayer1 ? elementalBondsRoomData.getPlayer1() : elementalBondsRoomData.getPlayer2();
        ElementalBondsPlayerData opponentElementalBondsPlayerData = isPlayer1 ? elementalBondsRoomData.getPlayer2() : elementalBondsRoomData.getPlayer1();

        ElementalBondsOperateLog elementalBondsOperateLog = new ElementalBondsOperateLog(selfElementalBondsPlayerData.getElementalBondsMatchData().getPlayerId(),
                opponentElementalBondsPlayerData.getElementalBondsMatchData().getPlayerId(), elementalBondsRoomData.getRoomId(), elementalBondsOperateLogType.getType(),
                elementalBondsLogParam, elementalBondsRoomData.getRound(), elementalBondsRoomData.getModeId(),
                elementalBondsRoomData.getGameData() != null ? elementalBondsRoomData.getGameData().getSceneId() : -1, selfElementalBondsPlayerData.getStep(),
                selfElementalBondsPlayerData.getElementalBondsCardData().getCardMp(), selfElementalBondsPlayerData.getScore());

        LogMgr.getInstance().addLog(elementalBondsOperateLog);
    }

    public void getRankInfo(ChannelElementalBondsUserKeyMsg userKey, ElementalBonds.ElementalBondsGetScoreLadderInfoRespMsg.Builder resp) {
        long playerId = userKey.getPlayerId();
        long serverId = userKey.getServerId();
        Long groupId = serverGroupMap.get(serverId);
        if (groupId == null) {
            resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_GROUP_NO_EXIST);
        }
        CrossElementalBondsGroupMgr groupMgr = groupMgrMap.get(groupId);
        if (groupMgr == null) {
            resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_GROUP_NO_EXIST);
            return;
        }
        if (resp.getRet() == 0) {
            int playerRankId = groupMgr.getPlayerRankId(playerId);
            ElementalBondsRankData playerRankData = groupMgr.getPlayerRankData(playerId);
            resp.setRank(playerRankId);
            resp.setRet(0);
        }
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_GET_SCORE_LADDER_INFO_VALUE % PbDefine.ToGamePbDefine, resp, userKey);
    }

    public void getRankList(long serverId) {
        Long groupId = serverGroupMap.get(serverId);
        if (groupId == null) {
            return;
        }
        CrossElementalBondsGroupMgr groupMgr = groupMgrMap.get(groupId);
        if (groupMgr == null) {
            return;
        }
        CrossElementalBonds.CrossElementalBondsGetRankRecordRespMsg.Builder resp = CrossElementalBonds.CrossElementalBondsGetRankRecordRespMsg.newBuilder();
        CopyOnWriteArrayList<ElementalBondsRankData> playerRankList = groupMgr.getPlayerRankList();
        if (!playerRankList.isEmpty()) {
            ElementalBondsRankData elementalBondsRankData = playerRankList.get(0);
            Rank.RankTempMsg.Builder topRank = Rank.RankTempMsg.newBuilder();
            topRank.setValue(String.valueOf(elementalBondsRankData.getScore()));
            long playerId = elementalBondsRankData.getPlayerId();
            PlayerShowData showData = PlayerRedisMgr.getInstance().getPlayerShowInfo(playerId);
            if (showData != null) {
                topRank.setPlayerCharacterImageMsg(PlayerInfoPb.builderPlayerCharacterImageMsg(showData));
            }
            int i = 1;
            for (ElementalBondsRankData bondsRankData : playerRankList) {
                resp.addPlayerId(bondsRankData.getPlayerId());
                resp.addRank(i++);
            }
        }

        YanQuMessage message = YanQuMessageUtils.buildMessage(ElementalBondsPb.ELEMENTAL_BONDS_GET_RANK_RECORD_FROM_CROSS_VALUE, resp);
        CrossAgentLinkedControl.send(serverId, message);
    }

    public void getRankList(long playerId, long serverId, int rankType, Rank.GetRankListResp.Builder resp) {
        Long groupId = serverGroupMap.get(serverId);
        if (groupId == null) {
            resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_GROUP_NO_EXIST);
            return;
        }
        CrossElementalBondsGroupMgr groupMgr = groupMgrMap.get(groupId);
        if (groupMgr == null) {
            resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_GROUP_NO_EXIST);
            return;
        }
        Rank.RankMsg.Builder builder = Rank.RankMsg.newBuilder();
        if (rankType == 1) {
            builder = groupMgr.getRankList(playerId, rankType);
            resp.setRankMsg(builder);
        } else if (rankType == 2) {
            long nowTime = TimeUtils.currentTimeMillis();
            if (nowTime < getRewardTime() + 5 * TimeUtils.MINUTE_MILLIS) {
                resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_FINALLY_RANK_NO_OPEN);
                return;
            }
            int rank = 0;
            for (ElementalBondsRankData rankData : this.rankList) {
                rank++;
                if (rankData.getPlayerId() == playerId) {
                    builder.setMyRank(rank);
                    builder.setMyValue(String.valueOf(rankData.getScore()));
                }
            }
            Rank.RankMsgList.Builder rankMsgList = Rank.RankMsgList.newBuilder();
            CrossElementalBondsPb.getRankBuilder(rankList, rankMsgList);
            builder.setRankList(rankMsgList.buildPartial().toByteString());
        }
        builder.setBigType(SystemId.ElementalBonds.getValue());
        builder.setRankType(rankType);
        resp.setRankMsg(builder);
        resp.setRet(0);
    }

    public long getRewardTime() {
        ElementalBondsSeasonConfig seasonConfig = seasonConfigMap.get(elementBondsSystemInfo.getSeasonId());
        return seasonConfig.getRewardBeginTime();
    }

    public void addSystemMessageMsgToList(WorldMessageTempMsg.Builder worldMessageTempMsgBuilder, long serverId) {
        if (worldMessageTempMsgBuilder.getType() != eMessageType.ElementalBonds.getValue()) {
            return;
        }
        // 获取玩家的分组ID
        long groupId = this.serverGroupMap.getOrDefault(serverId, 0L);
        if (groupId == 0L) {
            return;
        }
        synchronized (chatMessageMap) {
            if (!chatMessageMap.containsKey(groupId)) {
                chatMessageMap.put(groupId, new ArrayList<>());
            }
        }
        synchronized (chatMessageMap.get(groupId)) {
            List<WorldMessageTempMsg.Builder> worldMessageTempMsgBuilderList = chatMessageMap.get(groupId);
            if (worldMessageTempMsgBuilderList.size() >= MAX_MESSAGE_COUNT) {
                worldMessageTempMsgBuilderList.remove(0);
            }
            worldMessageTempMsgBuilderList.add(worldMessageTempMsgBuilder);
            sendSystemMessage(groupId, worldMessageTempMsgBuilder);
        }
    }

    public void sendSystemMessage(long groupId, WorldMessageTempMsg.Builder worldMessageTempMsgBuilder) {
        WorldMessageListMsg.Builder worldMessageListMsgBuilder = WorldMessageListMsg.newBuilder();
        worldMessageListMsgBuilder.addDataList(worldMessageTempMsgBuilder);
        CrossElementalBondsGroupMgr crossElementalBondsGroupMgr = groupMgrMap.get(groupId);
        if (crossElementalBondsGroupMgr == null) {
            return;
        }
        ElementalBondsGroupData elementalBondsGroupData = crossElementalBondsGroupMgr.getElementalBondsGroupData();
        List<Long> serverIdList = elementalBondsGroupData.getServerIdList();
        // 获取对应的聊天消息然后发送
        sendSystemMessage(0, serverIdList, worldMessageListMsgBuilder, new ArrayList<>());
    }

    public List<WorldMessageTempMsg.Builder> getSystemChatMessageList(long serverId) {
        List<WorldMessageTempMsg.Builder> worldMessageTempMsgBuilderList = new ArrayList<>(systemChatMessageList);
        long groupId = this.serverGroupMap.getOrDefault(serverId, 0L);
        if (groupId == 0L) {
            return worldMessageTempMsgBuilderList;
        }
        if (chatMessageMap.containsKey(groupId)) {
            worldMessageTempMsgBuilderList.addAll(new ArrayList<>(chatMessageMap.get(groupId)));
        }
        return worldMessageTempMsgBuilderList;
    }

    public void getUserData(ChannelElementalBondsUserKeyMsg userKey, ElementalBonds.ElementalBondsGetUserRespMsg.Builder resp) {
        String key = getKey(userKey);
        resp.setRet(0);
        ElementalBondsRoomData roomData = null;
        ElementalBondsMatchData matchData = roomUserMap.get(key);
        if (matchData != null) {
            roomData = roomMap.get(matchData.getRoomId());
        } else {
            resp.setUserType(0);
        }
        if (roomData != null) {
            if (isRoomPlaying(roomData)) {
                resp.setUserType(2);
            } else {
                resp.setUserType(1);
            }
        }
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_GET_USER_DATA_VALUE % PbDefine.ToGamePbDefine, resp, userKey);
    }

    public void sendRoundIdServerBack(long serverId) {
        syncedServerIdSet.add(serverId);
    }

    public void getRankAward(long serverId, long playerId, int rankType, Rank.GetRankAwardResp.Builder resp) {
        Long groupId = serverGroupMap.get(serverId);
        if (groupId == null) {
            resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_GROUP_NO_EXIST);
            return;
        }
        CrossElementalBondsGroupMgr groupMgr = groupMgrMap.get(groupId);
        if (groupMgr == null) {
            resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_GROUP_NO_EXIST);
            return;
        }
        if (!isCanRankAward()) {
            resp.setRet(GameErrorCode.E_ACTIVITY_NOT_IN_REWARD_PERIOD);
            return;
        }
        groupMgr.getRankAward(playerId, rankType, resp);
        syncRedPoint(playerId, serverId);
    }

    /**
     * 是否可领奖（时间）
     *
     * @return 是否可以领奖
     */
    public boolean isCanRankAward() {
        long curTime = TimeUtils.currentTimeMillis();
        ElementalBondsSeasonConfig bondsSeasonConfig = getElementalBondsSeasonConfig(curTime);
        if (bondsSeasonConfig == null) return false;
        return curTime >= bondsSeasonConfig.getRewardBeginTime() && curTime < this.elementBondsSystemInfo.getSeasonEndTime();//TODO
    }

    public void getRankStatus(long serverId, long playerId, int rankType, ActivityBase.RespGetActivityRankState.Builder resp) {
        Long groupId = serverGroupMap.get(serverId);
        if (groupId == null) {
            resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_GROUP_NO_EXIST);
            return;
        }
        CrossElementalBondsGroupMgr groupMgr = groupMgrMap.get(groupId);
        if (groupMgr == null) {
            resp.setRet(GameErrorCode.E_ELEMENTAL_BONDS_GROUP_NO_EXIST);
            return;
        }
        groupMgr.getRankState(playerId, rankType, resp);
    }

    //入口红点
    public boolean getPlayerRedPoint(long playerId, long serverId) {
        return canGetRankReward(playerId, serverId);
    }

    public boolean canGetRankReward(long playerId, long serverId) {
        if (!isCanRankAward()) return false;
        Long groupId = serverGroupMap.get(serverId);
        if (groupId == null) {
            return false;
        }
        CrossElementalBondsGroupMgr groupMgr = groupMgrMap.get(groupId);
        if (groupMgr == null) {
            return false;
        }
        if (!isCanRankAward()) {
            return false;
        }

        return groupMgr.hasReward(playerId);
    }

    private void syncRedPoint(long playerId, long serverId) {
        //红点
        ElementalBondsSyncRedPointInfoMsg.Builder builder = ElementalBondsSyncRedPointInfoMsg.newBuilder();
        //目前跨服只有一个红点
        boolean canGetRankReward = canGetRankReward(playerId, serverId);
        if (canGetRankReward) {
            ElementalBondsRedPointData.Builder msg = ElementalBondsRedPointData.newBuilder();
            msg.setRedPointType(ElementalBondsRedPointEnum.ElementalBondsRedPoint_RankReward);
            msg.setIsShow(true);
            builder.addCrossRedPointInfo(msg);
        }
        sendGamePacket(ElementalBondsPb.ELEMENTAL_BONDS_SYNC_RED_POINT_INFO_VALUE % PbDefine.ToGamePbDefine, builder, serverId, playerId);
    }

    //补发奖励返回
    public void sendRankAwardBack(long serverId) {
        Long groupId = serverGroupMap.get(serverId);
        if (groupId == null) {
            return;
        }
        CrossElementalBondsGroupMgr groupMgr = groupMgrMap.get(groupId);
        if (groupMgr == null) {
            return;
        }
        groupMgr.reissueRewardBack();
    }
    //脚本
    public Object getLock() {
        return lock;
    }

    public void tryGroup() {
        Stopwatch stopwatch = Stopwatch.createStarted();
//
//        ChannelIdReq channelIdReq = new ChannelIdReq();
//        channelIdReq.setChannelId(31);
//
//        String rspString = HttpHelper.doPostJson("http://10.1.103.55:8010/server/getServerAndAddressInfoList", JsonUtils.toJSONString(channelIdReq), "UTF-8");
//        //保存到本地
//        File file = new File("serverAndAddressInfoList.json");
//        String filePath = "D:\\server.txt";
//        FileUtils.appendToFile(filePath, rspString);
        // 读取本地文件
        String filePath = "D:\\server.txt"; // 文件路径
        String rspString = null;
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                rspString = line;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        ServerAndAddressInfoList serverAndAddressInfoList = JsonUtils.parseObject(rspString, ServerAndAddressInfoList.class, false);

        synchronized (lock) {
            long now = TimeUtils.currentTimeMillis();
            // if (TimeUtils.isSameWeek(elementBondsSystemInfo.getGroupTime())) return;
            //判断是否跨赛季了检查赛季切换
            long zeroSubTime = now - DateHelper.getZeroTimeStamp(now);
            //TODO:分组期
            boolean isNewSeason = false;
            ElementalBondsSeasonConfig bondsSeasonConfig = getElementalBondsSeasonConfig(now);
            if (bondsSeasonConfig == null) {
                return;
            }
            if (bondsSeasonConfig.getSeasonId() != elementBondsSystemInfo.getSeasonId()) {
                LogUtils.COMMON.info("handle elementalbonds season change...");
                reloadSeasonConfig();
                isNewSeason = true;
            }
            // 2.算出所有区服的轮次
            //ServerAndAddressInfoList serverAndAddressInfoList = CrossSystemGroupRule.getServerAndAddressInfoList("CrossElementBonds");
            if (serverAndAddressInfoList == null || serverAndAddressInfoList.getServerAndAddressInfoMap().isEmpty()) {
                return;
            }

            Map<Integer, RoundServerList> newRoundMap = new HashMap<>();
            if (isNewSeason) {
                //清数据
                serverGroupMap.clear();
                groupMgrMap.clear();
                MongoRepository.deleteList(new Query(), ElementalBondsGroupData.class);
                elementBondsSystemInfo.setSeasonStartTime(bondsSeasonConfig.getBeginShowTime());
                elementBondsSystemInfo.setSeasonEndTime(bondsSeasonConfig.getEndShowTime());
                elementBondsSystemInfo.setServerWaitMap(new ConcurrentHashMap<>());
                elementBondsSystemInfo.setSceneList(new ArrayList<>());
                elementBondsSystemInfo.setSceneWeightMap(new ConcurrentHashMap<>());

                elementBondsSystemInfo.setSeasonStartTime(bondsSeasonConfig.getBeginShowTime());
                elementBondsSystemInfo.setSeasonEndTime(bondsSeasonConfig.getEndShowTime());
            }
            Set<Long> hasGroupServerIdList = new HashSet<>();
            for (CrossElementalBondsGroupMgr groupInfo : groupMgrMap.values()) {
                hasGroupServerIdList.addAll(groupInfo.getServerIdList());
            }
            long time = TimeUtils.getWeekBeginTimestamp() + 4 * TimeUtils.DAY_MILLIS;
            for (ServerAndAddressInfo serverAndAddressInfo : serverAndAddressInfoList.getServerAndAddressInfoMap().values()) {
                if (now < serverAndAddressInfo.getServerInfo().getOpenTime()) {
                    continue;
                }
                // 补充规则：开放时间跟分组时间是同一天就不分组
                if (TimeUtils.isSameDay(now, serverAndAddressInfo.getServerInfo().getOpenTime())) {
                    continue;
                }
                if (serverAndAddressInfo.getServerAddress() == null || serverAndAddressInfo.getServerAddress().getCrossServerIds() == null) {
                    continue;
                }
//                if (!serverAndAddressInfo.getServerAddress().getCrossServerIds().contains(CrossServerConfigMgr.getInstance().getConfig().getServerAddressRsp().getCrossServerId())) {
//                    continue;
//                }
                long serverId = serverAndAddressInfo.getServerInfo().getServerId();
                if (hasGroupServerIdList.contains(serverId)) {
                    // 已有分组
                    continue;
                }

                if (elementBondsSystemInfo.getServerWaitMap().containsKey(serverId)) {
                    //  需要特殊处理
                    continue;
                }

                int round = ServerIdMgr.calRoundNum(serverAndAddressInfo.getServerInfo().getOpenTime(), time, serverAndAddressInfo.getServerInfo().getFirstRound(), BusinessServerParamConfigParam.CHANNEL_SECOND_ROUND_CONFIG);
                RoundServerList roundServerList = null;
                roundServerList = newRoundMap.computeIfAbsent(round, k -> new RoundServerList());
                roundServerList.setRound(round);
                roundServerList.getServerList().add(serverId);
            }
            if (isNewSeason) {
                Map<Long, ElementalBondsGroupData> groupDataMap = roundGroup(newRoundMap, now, 0);
                for (ElementalBondsGroupData elementalBondsGroupData : groupDataMap.values()) {
                    CrossElementalBondsGroupMgr groupMgr = new CrossElementalBondsGroupMgr(elementalBondsGroupData);
                    groupMgrMap.put(elementalBondsGroupData.getGroupId(), groupMgr);
                    for (Long serverId : elementalBondsGroupData.getServerIdList()) {
                        serverGroupMap.put(serverId, elementalBondsGroupData.getGroupId());
                    }

                }
            } else {
                //判断分组是否截至
                long elementalBondsGroupEndday = Long.parseLong(BusinessServerParamConfigParam.ELEMENTAL_BONDS_GROUP_ENDDAY) * TimeUtils.MINUTE_MILLIS;
                doGroupAndMerge(newRoundMap, now);

            }
            //强制刷新
//            checkRefreshScene(true);
//            同步到区服
//            syncGroupToServerOnce();
//            elementBondsSystemInfo.setGroupTime(now);
            elementBondsSystemInfo.setSeasonId(bondsSeasonConfig.getSeasonId());
        }

//        long curTime = TimeUtils.currentTimeMillis();
//
//        long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
//        logger.info("夺魁分组完成， 耗时: {} ms", elapsed);
//        String filePathx = "D://group.txt";
//
//        Map<Long, ElementalBondsGroupData> groupDataMap = new HashMap<>();
//        for (CrossElementalBondsGroupMgr groupMgr : groupMgrMap.values()) {
//            ElementalBondsGroupData elementalBondsGroupData = groupMgr.getElementalBondsGroupData();
//            String tmp = groupMgr.getGroupId() + ":" + JsonUtils.toJSONString(elementalBondsGroupData) + "  ";
//            FileUtils.appendToFile(filePathx, tmp);
//        }

    }

}
